┏━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┓

┃ Strategy ┃ Trades ┃ Avg Profit % ┃ Tot Profit USDC ┃ Tot Profit % ┃     Avg Duration ┃  Win  Draw  Loss  Win% ┃            Drawdown ┃
┡━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━┩
│    Empty │     45 │         3.05 │         903.533 │        90.35 │ 15 days, 1:43:00 │   44     0     1  97.8 │ 1300.8 USDC  40.60% │
└──────────┴────────┴──────────────┴─────────────────┴──────────────┴──────────────────┴────────────────────────┴─────────────────────┘
This commit is contained in:
Jérôme Delacotte
2026-02-20 21:54:17 +01:00
parent 18fa5151e8
commit a228e56172
3 changed files with 458 additions and 172 deletions

View File

@@ -4,6 +4,15 @@
"roi": { "roi": {
"0": 10 "0": 10
}, },
"stoploss": {
"stoploss": -1.0
},
"trailing": {
"trailing_stop": false,
"trailing_stop_positive": null,
"trailing_stop_positive_offset": 0.0,
"trailing_only_offset_is_reached": false
},
"max_open_trades": { "max_open_trades": {
"max_open_trades": 20 "max_open_trades": 20
}, },
@@ -21,30 +30,15 @@
"buy_real_num1": 1.0, "buy_real_num1": 1.0,
"buy_real_num2": -0.4 "buy_real_num2": -0.4
}, },
"protection": {
"stop_buying_indicator": "stop_buying12_1d",
"stoploss_indicator": "stop_buying12_1d",
"stoploss_timeperiod": "12"
},
"sell": { "sell": {
"sell_crossed_indicator0": "sma24_deriv2", "sell_score_indicator": "sma48_score"
"sell_crossed_indicator1": "sma48_score",
"sell_crossed_indicator2": "sma24_score",
"sell_indicator0": "sma5_deriv2",
"sell_indicator1": "sma60_score",
"sell_indicator2": "sma60_score",
"sell_operator0": "<",
"sell_operator1": ">",
"sell_operator2": "CB",
"sell_real_num0": 0.7,
"sell_real_num1": 0.5,
"sell_real_num2": 0.9
},
"stoploss": {
"stoploss": -1
},
"trailing": {
"trailing_stop": false,
"trailing_stop_positive": 0.055,
"trailing_stop_positive_offset": 0.106,
"trailing_only_offset_is_reached": false
} }
}, },
"ft_stratparam_v": 1, "ft_stratparam_v": 1,
"export_time": "2026-02-19 19:14:20.539321+00:00" "export_time": "2026-02-20 20:19:59.741160+00:00"
} }

466
Empty.py
View File

@@ -25,6 +25,8 @@ from random import shuffle
timeperiods = [3, 5, 12, 24, 48, 60] timeperiods = [3, 5, 12, 24, 48, 60]
score_indicators = list()
stoploss_indicators = list()
god_genes_with_timeperiod = list() god_genes_with_timeperiod = list()
for timeperiod in timeperiods: for timeperiod in timeperiods:
# god_genes_with_timeperiod.append(f'max{timeperiod}') # god_genes_with_timeperiod.append(f'max{timeperiod}')
@@ -34,11 +36,20 @@ for timeperiod in timeperiods:
god_genes_with_timeperiod.append(f"sma{timeperiod}_deriv1") god_genes_with_timeperiod.append(f"sma{timeperiod}_deriv1")
god_genes_with_timeperiod.append(f"sma{timeperiod}_deriv2") god_genes_with_timeperiod.append(f"sma{timeperiod}_deriv2")
god_genes_with_timeperiod.append(f"sma{timeperiod}_score") god_genes_with_timeperiod.append(f"sma{timeperiod}_score")
# stoploss_indicators.append(f"stop_buying{timeperiod}")
stoploss_indicators.append(f"stop_buying{timeperiod}_1d")
score_indicators.append(f"sma{timeperiod}_score")
# score_indicators.append(f"sma{timeperiod}_score_1d")
# god_genes_with_timeperiod.append(f"sma{timeperiod}_trend_up") # god_genes_with_timeperiod.append(f"sma{timeperiod}_trend_up")
# god_genes_with_timeperiod.append(f"sma{timeperiod}_trend_down") # god_genes_with_timeperiod.append(f"sma{timeperiod}_trend_down")
# god_genes_with_timeperiod.append(f"sma{timeperiod}_trend_change_up") # god_genes_with_timeperiod.append(f"sma{timeperiod}_trend_change_up")
# god_genes_with_timeperiod.append(f"sma{timeperiod}_trend_change_down") # god_genes_with_timeperiod.append(f"sma{timeperiod}_trend_change_down")
print(stoploss_indicators)
operators = [ operators = [
"D", # Disabled gene "D", # Disabled gene
">", # Indicator, bigger than cross indicator ">", # Indicator, bigger than cross indicator
@@ -249,14 +260,13 @@ class Empty(IStrategy):
# Stoploss: # Stoploss:
stoploss = -1 stoploss = -1
trailing_stop = True trailing_stop = False
trailing_stop_positive = 0.15 # trailing_stop_positive = 0.15
trailing_stop_positive_offset = 0.20 # trailing_stop_positive_offset = 0.20
trailing_only_offset_is_reached = True # trailing_only_offset_is_reached = False
position_adjustment_enable = True position_adjustment_enable = True
use_custom_stoploss = True use_custom_stoploss = False
#max_open_trades = 3 #max_open_trades = 3
@@ -295,6 +305,7 @@ class Empty(IStrategy):
'last_date': 0, 'last_date': 0,
'stop': False, 'stop': False,
'max_profit': 0, 'max_profit': 0,
'last_profit': 0,
'total_amount': 0, 'total_amount': 0,
'has_gain': 0, 'has_gain': 0,
'force_sell': False, 'force_sell': False,
@@ -356,22 +367,27 @@ class Empty(IStrategy):
buy_real_num2 = DecimalParameter(-1, 1, decimals=DECIMALS, default=0, space='buy') buy_real_num2 = DecimalParameter(-1, 1, decimals=DECIMALS, default=0, space='buy')
# Sell Hyperoptable Parameters/Spaces. # Sell Hyperoptable Parameters/Spaces.
sell_crossed_indicator0 = CategoricalParameter(god_genes_with_timeperiod, default="CDLSHOOTINGSTAR-150", space='sell') # sell_crossed_indicator0 = CategoricalParameter(god_genes_with_timeperiod, default="CDLSHOOTINGSTAR-150", space='sell')
sell_crossed_indicator1 = CategoricalParameter(god_genes_with_timeperiod, default="MAMA-1-100", space='sell') # sell_crossed_indicator1 = CategoricalParameter(god_genes_with_timeperiod, default="MAMA-1-100", space='sell')
sell_crossed_indicator2 = CategoricalParameter(god_genes_with_timeperiod, default="CDLMATHOLD-6", space='sell') # sell_crossed_indicator2 = CategoricalParameter(god_genes_with_timeperiod, default="CDLMATHOLD-6", space='sell')
#
# sell_indicator0 = CategoricalParameter(god_genes_with_timeperiod, default="CDLUPSIDEGAP2CROWS-5", space='sell')
# sell_indicator1 = CategoricalParameter(god_genes_with_timeperiod, default="CDLHARAMICROSS-150", space='sell')
# sell_indicator2 = CategoricalParameter(god_genes_with_timeperiod, default="CDL2CROWS-5", space='sell')
#
# sell_operator0 = CategoricalParameter(operators, default="<R", space='sell')
# sell_operator1 = CategoricalParameter(operators, default="D", space='sell')
# sell_operator2 = CategoricalParameter(operators, default="/>R", space='sell')
#
# sell_real_num0 = DecimalParameter(-1, 1, decimals=DECIMALS, default=0, space='sell')
# sell_real_num1 = DecimalParameter(-1, 1, decimals=DECIMALS, default=0, space='sell')
# sell_real_num2 = DecimalParameter(-1, 1, decimals=DECIMALS, default=0, space='sell')
sell_indicator0 = CategoricalParameter(god_genes_with_timeperiod, default="CDLUPSIDEGAP2CROWS-5", space='sell') sell_score_indicator = CategoricalParameter(score_indicators, default="sma24_score", space='sell')
sell_indicator1 = CategoricalParameter(god_genes_with_timeperiod, default="CDLHARAMICROSS-150", space='sell')
sell_indicator2 = CategoricalParameter(god_genes_with_timeperiod, default="CDL2CROWS-5", space='sell')
sell_operator0 = CategoricalParameter(operators, default="<R", space='sell')
sell_operator1 = CategoricalParameter(operators, default="D", space='sell')
sell_operator2 = CategoricalParameter(operators, default="/>R", space='sell')
sell_real_num0 = DecimalParameter(-1, 1, decimals=DECIMALS, default=0, space='sell')
sell_real_num1 = DecimalParameter(-1, 1, decimals=DECIMALS, default=0, space='sell')
sell_real_num2 = DecimalParameter(-1, 1, decimals=DECIMALS, default=0, space='sell')
stoploss_indicator = CategoricalParameter(stoploss_indicators, default="stop_buying12_1d", space='protection')
stop_buying_indicator = CategoricalParameter(stoploss_indicators, default="stop_buying12_1d", space='protection')
stoploss_timeperiod = CategoricalParameter(timeperiods, default="12", space='protection')
def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float, def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
proposed_stake: float, min_stake: float, max_stake: float, proposed_stake: float, min_stake: float, max_stake: float,
@@ -384,7 +400,21 @@ class Empty(IStrategy):
if (self.pairs[pair]['first_amount'] > 0): if (self.pairs[pair]['first_amount'] > 0):
amount = min(self.wallets.get_available_stake_amount(), self.pairs[pair]['first_amount']) amount = min(self.wallets.get_available_stake_amount(), self.pairs[pair]['first_amount'])
else: else:
amount = self.wallets.get_available_stake_amount() / 4 # range_min = last_candle[f"min{self.stoploss_timeperiod.value}_1d"]
# range_max = last_candle[f"max{self.stoploss_timeperiod.value}_1d"]
#
# if range_max == range_min:
# return -0.1 # sécurité
#
# range_pos = (last_candle['close'] - range_min) / (range_max - range_min)
range_pos = last_candle[f"range_pos"]
sl_min = self.wallets.get_available_stake_amount() / 2
sl_max = self.wallets.get_available_stake_amount() / 6
amount = sl_min + (1 - range_pos) * (sl_max - sl_min)
# amount = self.wallets.get_available_stake_amount() / 8
return min(amount, self.wallets.get_available_stake_amount()) return min(amount, self.wallets.get_available_stake_amount())
def adjust_trade_position(self, trade: Trade, current_time: datetime, def adjust_trade_position(self, trade: Trade, current_time: datetime,
@@ -442,7 +472,7 @@ class Empty(IStrategy):
drop_from_last_entry = (current_rate - last_entry_price) / last_entry_price drop_from_last_entry = (current_rate - last_entry_price) / last_entry_price
if drop_from_last_entry <= -0.05: if drop_from_last_entry <= -0.025 and last_candle['min60'] == last_candle_3['min60']:
# stake_amount = trade.stake_amount # stake_amount = trade.stake_amount
print(f"adjust {current_time} {stake_amount}") print(f"adjust {current_time} {stake_amount}")
print(f"adjust {pair} {current_time} dispo={dispo} amount={stake_amount} rate={current_rate}") print(f"adjust {pair} {current_time} dispo={dispo} amount={stake_amount} rate={current_rate}")
@@ -462,7 +492,19 @@ class Empty(IStrategy):
last_candle_2 = dataframe.iloc[-2].squeeze() last_candle_2 = dataframe.iloc[-2].squeeze()
last_candle_3 = dataframe.iloc[-3].squeeze() last_candle_3 = dataframe.iloc[-3].squeeze()
condition = True #(last_candle[f"{indic_5m}_deriv1"] >= indic_deriv1_5m) and (last_candle[f"{indic_5m}_deriv2"] >= indic_deriv2_5m) condition = True
if self.pairs[pair]['last_trade'] and self.pairs[pair]['last_trade'].close_date:
# base cooldown = n bougies / cooldown proportionnel au profit
# bougies de plus par %
cooldown_candles = 12 + 6 * (int(self.pairs[pair]['last_profit'] / 0.01)) # réglable
# temps depuis la fermeture
candles_since_close = ((current_time - self.pairs[pair]['last_trade'].close_date).total_seconds() / 3600) # seconds / heure
condition = (candles_since_close >= cooldown_candles)
print(f"Cool down {round(self.pairs[pair]['last_profit'], 3)} {cooldown_candles} {candles_since_close}")
# self.should_enter_trade(pair, last_candle, current_time) # self.should_enter_trade(pair, last_candle, current_time)
allow_to_buy = (condition and not self.pairs[pair]['stop']) | (entry_tag == 'force_entry') allow_to_buy = (condition and not self.pairs[pair]['stop']) | (entry_tag == 'force_entry')
@@ -482,6 +524,8 @@ class Empty(IStrategy):
self.pairs[pair]['last_candle'] = last_candle self.pairs[pair]['last_candle'] = last_candle
self.pairs[pair]['count_of_buys'] = 1 self.pairs[pair]['count_of_buys'] = 1
self.pairs[pair]['current_profit'] = 0 self.pairs[pair]['current_profit'] = 0
self.pairs[pair]['last_profit'] = 0
self.pairs[pair]['last_trade'] = None
self.pairs[pair]['last_max'] = max(last_candle['close'], self.pairs[pair]['last_max']) self.pairs[pair]['last_max'] = max(last_candle['close'], self.pairs[pair]['last_max'])
self.pairs[pair]['last_min'] = min(last_candle['close'], self.pairs[pair]['last_min']) self.pairs[pair]['last_min'] = min(last_candle['close'], self.pairs[pair]['last_min'])
self.pairs[pair]['last_date'] = current_time self.pairs[pair]['last_date'] = current_time
@@ -492,7 +536,7 @@ class Empty(IStrategy):
stake_amount = self.adjust_stake_amount(pair, last_candle) stake_amount = self.adjust_stake_amount(pair, last_candle)
self.pairs[pair]['first_amount'] = stake_amount self.pairs[pair]['first_amount'] = stake_amount
self.pairs[pair]['total_amount'] = stake_amount self.pairs[pair]['total_amount'] = stake_amount
print(f"Buy {pair} {current_time} {entry_tag} dispo={dispo} amount={amount} rate={rate} rate={rate}") print(f"Buy {pair} {current_time} {entry_tag} dispo={dispo} amount={stake_amount} rate={rate} rate={rate}")
# self.log_trade( # self.log_trade(
# last_candle=last_candle, # last_candle=last_candle,
@@ -519,7 +563,7 @@ class Empty(IStrategy):
profit = trade.calc_profit(rate) profit = trade.calc_profit(rate)
force = self.pairs[pair]['force_sell'] force = self.pairs[pair]['force_sell']
allow_to_sell = (last_candle['hapercent'] < 0 and profit > 0) or force or (exit_reason == 'force_exit') or (exit_reason == 'stop_loss') allow_to_sell = (last_candle['hapercent'] < 0 and profit > 0) or force or (exit_reason == 'force_exit') or (exit_reason == 'stop_loss') or (exit_reason == 'trailing_stop_loss')
minutes = int(round((current_time - trade.date_last_filled_utc).total_seconds() / 60, 0)) minutes = int(round((current_time - trade.date_last_filled_utc).total_seconds() / 60, 0))
@@ -530,7 +574,7 @@ class Empty(IStrategy):
profit = trade.calc_profit(rate) profit = trade.calc_profit(rate)
self.pairs[pair]['previous_profit'] = profit self.pairs[pair]['previous_profit'] = profit
dispo = round(self.wallets.get_available_stake_amount()) dispo = round(self.wallets.get_available_stake_amount())
print(f"Sell {pair} {current_time} {exit_reason} dispo={dispo} amount={amount} rate={rate} open_rate={trade.open_rate} profit={profit}") print(f"Sell {pair} {current_time} {exit_reason} dispo={dispo} rate={rate} open_rate={trade.open_rate} profit={profit}")
# self.log_trade( # self.log_trade(
# last_candle=last_candle, # last_candle=last_candle,
# date=current_time, # date=current_time,
@@ -541,6 +585,7 @@ class Empty(IStrategy):
# dispo=dispo, # dispo=dispo,
# profit=round(profit, 2) # profit=round(profit, 2)
# ) # )
self.pairs[pair]['first_amount'] = 0
self.pairs[pair]['force_sell'] = False self.pairs[pair]['force_sell'] = False
self.pairs[pair]['has_gain'] = 0 self.pairs[pair]['has_gain'] = 0
self.pairs[pair]['current_profit'] = 0 self.pairs[pair]['current_profit'] = 0
@@ -551,42 +596,71 @@ class Empty(IStrategy):
self.pairs[pair]['last_date'] = current_time self.pairs[pair]['last_date'] = current_time
self.pairs[pair]['last_trade'] = self.pairs[pair]['current_trade'] self.pairs[pair]['last_trade'] = self.pairs[pair]['current_trade']
self.pairs[pair]['current_trade'] = None self.pairs[pair]['current_trade'] = None
# else: else:
# print(f"STOP triggered for {pair} ({exit_reason}) but condition blocked", "warning") print(f"{current_time} STOP triggered for {pair} ({exit_reason}) but condition blocked", "warning")
return (allow_to_sell) | (exit_reason == 'force_exit') | (exit_reason == 'stop_loss') | force return (allow_to_sell) | (exit_reason == 'force_exit') | (exit_reason == 'stop_loss') | force
def custom_exit(self, pair, trade, current_time, def custom_exit(self, pair, trade, current_time, current_rate, current_profit, **kwargs):
current_rate, current_profit, **kwargs):
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
last_row = dataframe.iloc[-1] last_candle = dataframe.iloc[-1]
before_last_candle = dataframe.iloc[-2]
self.pairs[pair]['current_trade'] = trade self.pairs[pair]['current_trade'] = trade
momentum = last_row["sma24_score"] momentum = last_candle[self.sell_score_indicator.value]
# Si momentum fort → on laisse courir # Si momentum fort → on laisse courir
if momentum > 1: if momentum > 1:
return None return None
# Si momentum faiblit → on prend profit plus tôt # Si momentum faiblit → on prend profit plus tôt
if momentum < 0 and current_profit > 0.02: if momentum < 0 and current_profit > 0.02 and last_candle[self.sell_score_indicator.value] < before_last_candle[self.sell_score_indicator.value]\
and last_candle['close'] < last_candle['sma5']:
self.pairs[pair]['last_profit'] = current_profit
return "momentum_drop" return "momentum_drop"
return None return None
def custom_stoploss(self, pair, trade, current_time, # def custom_stoploss(self, pair, trade, current_time, current_rate, current_profit, **kwargs):
current_rate, current_profit, **kwargs): # dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
# last_candle = dataframe.iloc[-1]
if current_profit < - 0.1 and self.wallets.get_available_stake_amount(): # profit = trade.calc_profit(current_rate)
return -0.1 #
# # print(f'test stop loss {self.stoploss} {last_candle["stop_buying12_1d"]}')
return -1 # if last_candle[self.stoploss_indicator.value] and (trade.nr_of_successful_entries >= 4 or self.wallets.get_available_stake_amount() < 300): # and profit < - 30 :
# range_min = last_candle[f"min{self.stoploss_timeperiod.value}_1d"]
# range_max = last_candle[f"max{self.stoploss_timeperiod.value}_1d"]
#
# if range_max == range_min:
# print(f'ranges={range_min}')
# return -0.1 # sécurité
#
# range_pos = (current_rate - range_min) / (range_max - range_min)
#
# if (range_pos > 1):
# return -1
#
# sl_min = -0.02
# sl_max = -0.1 #self.stoploss
#
# dynamic_sl = (sl_min + (1 - range_pos) * (sl_max - sl_min))
#
# print(f'{current_time} Loss ranges={round(range_min,0)} {round(range_max, 0)} range_pos={round(range_pos, 3)} dynamic_sl={round(dynamic_sl, 3)} '
# f'profit={profit} current={current_profit} {self.stoploss_indicator.value} {self.stoploss_timeperiod.value} {last_candle[self.stoploss_indicator.value]}')
#
# return dynamic_sl
#
# return -1
def informative_pairs(self): def informative_pairs(self):
# get access to all pairs available in whitelist.
pairs = self.dp.current_whitelist()
informative_pairs = [(pair, '1d') for pair in pairs]
# informative_pairs += [(pair, '1h') for pair in pairs]
return [] return informative_pairs
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
pair = metadata['pair']
dataframe = dataframe.copy() dataframe = dataframe.copy()
heikinashi = qtpylib.heikinashi(dataframe) heikinashi = qtpylib.heikinashi(dataframe)
dataframe['haopen'] = heikinashi['open'] dataframe['haopen'] = heikinashi['open']
@@ -602,19 +676,64 @@ class Empty(IStrategy):
dataframe[f"sma{timeperiod}"] = dataframe['mid'].ewm(span=timeperiod, adjust=False).mean() dataframe[f"sma{timeperiod}"] = dataframe['mid'].ewm(span=timeperiod, adjust=False).mean()
self.calculeDerivees(dataframe, f"sma{timeperiod}", timeframe=self.timeframe, ema_period=timeperiod) self.calculeDerivees(dataframe, f"sma{timeperiod}", timeframe=self.timeframe, ema_period=timeperiod)
# ###################################################################################################### dataframe[f'stop_buying{timeperiod}_deb'] = (dataframe[f'sma{timeperiod}_trend_change_down'] == 1)
dataframe['stop_buying_deb'] = (dataframe['sma60_trend_change_down'] == 1) dataframe[f'stop_buying{timeperiod}_end'] = (dataframe[f'sma{timeperiod}_trend_change_up'] == 1)
dataframe['stop_buying_end'] = (dataframe['sma60_trend_change_up'] == 1) latched = np.zeros(len(dataframe), dtype=bool)
latched = np.zeros(len(dataframe), dtype=bool)
for i in range(1, len(dataframe)): for i in range(1, len(dataframe)):
if dataframe['stop_buying_deb'].iloc[i]: if dataframe[f'stop_buying{timeperiod}_deb'].iloc[i]:
latched[i] = True latched[i] = True
elif dataframe['stop_buying_end'].iloc[i]: elif dataframe[f'stop_buying{timeperiod}_end'].iloc[i]:
latched[i] = False latched[i] = False
else: else:
latched[i] = latched[i - 1] latched[i] = latched[i - 1]
dataframe['stop_buying'] = latched dataframe[f'stop_buying{timeperiod}'] = latched
# ######################################################################################################
################### INFORMATIVE 1d
informative = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe="1d")
# informative = self.populateDataframe(informative, timeframe='1d')
# heikinashi = qtpylib.heikinashi(informative)
# informative['haopen'] = heikinashi['open']
# informative['haclose'] = heikinashi['close']
# informative['hapercent'] = (informative['haclose'] - informative['haopen']) / informative['haclose']
informative['mid'] = informative['open'] + (informative['close'] - informative['open']) / 2
for timeperiod in timeperiods:
informative[f'max{timeperiod}'] = talib.MAX(informative['close'], timeperiod=timeperiod)
informative[f'min{timeperiod}'] = talib.MIN(informative['close'], timeperiod=timeperiod)
# informative[f"range{timeperiod}"] = ((informative["close"] - informative[f'min{timeperiod}']) / (informative[f'max{timeperiod}'] - informative[f'min{timeperiod}']))
# informative[f"percent{timeperiod}"] = informative['close'].pct_change(timeperiod)
informative[f"sma{timeperiod}"] = informative['mid'].ewm(span=timeperiod, adjust=False).mean()
self.calculeDerivees(informative, f"sma{timeperiod}", timeframe=self.timeframe, ema_period=timeperiod)
informative[f'stop_buying_deb{timeperiod}'] = (informative[f'sma{timeperiod}_trend_change_down'] == 1)
informative[f'stop_buying_end{timeperiod}'] = (informative[f'sma{timeperiod}_trend_change_up'] == 1)
latched = np.zeros(len(informative), dtype=bool)
for i in range(1, len(informative)):
if informative[f'stop_buying_deb{timeperiod}'].iloc[i]:
latched[i] = True
elif informative[f'stop_buying_end{timeperiod}'].iloc[i]:
latched[i] = False
else:
latched[i] = latched[i - 1]
informative[f'stop_buying{timeperiod}'] = latched
dataframe = merge_informative_pair(dataframe, informative, self.timeframe, "1d", ffill=True)
# ######################################################################################################
range_min = dataframe[f"min12_1d"]
range_max = dataframe[f"max48"]
dataframe[f"range_pos"] = (dataframe['close'] - range_min) / (range_max - range_min)
# récupérer le dernier trade fermé
trades = Trade.get_trades_proxy(pair=pair,is_open=False)
if trades:
last_trade = trades[-1]
self.pairs[pair]['last_profit'] = last_trade.close_profit # ex: 0.12 = +12%
self.pairs[pair]['last_trade'] = last_trade
return dataframe return dataframe
@@ -631,51 +750,52 @@ class Empty(IStrategy):
conditions = list() conditions = list()
# print(dataframe.columns) # # print(dataframe.columns)
#
# buy_indicator = self.buy_indicator0.value
# buy_crossed_indicator = self.buy_crossed_indicator0.value
# buy_operator = self.buy_operator0.value
# buy_real_num = self.buy_real_num0.value
# condition, dataframe = condition_generator(
# dataframe,
# buy_operator,
# buy_indicator,
# buy_crossed_indicator,
# buy_real_num
# )
# conditions.append(condition)
# # backup
# buy_indicator = self.buy_indicator1.value
# buy_crossed_indicator = self.buy_crossed_indicator1.value
# buy_operator = self.buy_operator1.value
# buy_real_num = self.buy_real_num1.value
#
# condition, dataframe = condition_generator(
# dataframe,
# buy_operator,
# buy_indicator,
# buy_crossed_indicator,
# buy_real_num
# )
# conditions.append(condition)
#
# buy_indicator = self.buy_indicator2.value
# buy_crossed_indicator = self.buy_crossed_indicator2.value
# buy_operator = self.buy_operator2.value
# buy_real_num = self.buy_real_num2.value
# condition, dataframe = condition_generator(
# dataframe,
# buy_operator,
# buy_indicator,
# buy_crossed_indicator,
# buy_real_num
# )
# conditions.append(condition)
conditions.append((dataframe[self.stop_buying_indicator.value] == False))
buy_indicator = self.buy_indicator0.value # conditions.append(dataframe['hapercent'] > 0)
buy_crossed_indicator = self.buy_crossed_indicator0.value # # conditions.append(dataframe[f"range_pos"] <= 0.5)
buy_operator = self.buy_operator0.value # conditions.append(dataframe[f"sma5_deriv1"] > 0)
buy_real_num = self.buy_real_num0.value
condition, dataframe = condition_generator(
dataframe,
buy_operator,
buy_indicator,
buy_crossed_indicator,
buy_real_num
)
conditions.append(condition)
# backup
buy_indicator = self.buy_indicator1.value
buy_crossed_indicator = self.buy_crossed_indicator1.value
buy_operator = self.buy_operator1.value
buy_real_num = self.buy_real_num1.value
condition, dataframe = condition_generator(
dataframe,
buy_operator,
buy_indicator,
buy_crossed_indicator,
buy_real_num
)
conditions.append(condition)
buy_indicator = self.buy_indicator2.value
buy_crossed_indicator = self.buy_crossed_indicator2.value
buy_operator = self.buy_operator2.value
buy_real_num = self.buy_real_num2.value
condition, dataframe = condition_generator(
dataframe,
buy_operator,
buy_indicator,
buy_crossed_indicator,
buy_real_num
)
conditions.append(condition)
conditions.append((dataframe['stop_buying'] == False))
# conditions.append((dataframe['min60'].shift(5) == dataframe['min60']))
# conditions.append((dataframe['low'] < dataframe['min60']))
print(f"BUY indicators tested \n" print(f"BUY indicators tested \n"
f"{self.buy_indicator0.value} {self.buy_crossed_indicator0.value} {self.buy_operator0.value} {self.buy_real_num0.value} \n" f"{self.buy_indicator0.value} {self.buy_crossed_indicator0.value} {self.buy_operator0.value} {self.buy_real_num0.value} \n"
@@ -692,57 +812,57 @@ class Empty(IStrategy):
return dataframe return dataframe
def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
conditions = list() # conditions = list()
# TODO: Its not dry code! # # TODO: Its not dry code!
sell_indicator = self.sell_indicator0.value # sell_indicator = self.sell_indicator0.value
sell_crossed_indicator = self.sell_crossed_indicator0.value # sell_crossed_indicator = self.sell_crossed_indicator0.value
sell_operator = self.sell_operator0.value # sell_operator = self.sell_operator0.value
sell_real_num = self.sell_real_num0.value # sell_real_num = self.sell_real_num0.value
condition, dataframe = condition_generator( # condition, dataframe = condition_generator(
dataframe, # dataframe,
sell_operator, # sell_operator,
sell_indicator, # sell_indicator,
sell_crossed_indicator, # sell_crossed_indicator,
sell_real_num # sell_real_num
) # )
conditions.append(condition) # conditions.append(condition)
#
sell_indicator = self.sell_indicator1.value # sell_indicator = self.sell_indicator1.value
sell_crossed_indicator = self.sell_crossed_indicator1.value # sell_crossed_indicator = self.sell_crossed_indicator1.value
sell_operator = self.sell_operator1.value # sell_operator = self.sell_operator1.value
sell_real_num = self.sell_real_num1.value # sell_real_num = self.sell_real_num1.value
condition, dataframe = condition_generator( # condition, dataframe = condition_generator(
dataframe, # dataframe,
sell_operator, # sell_operator,
sell_indicator, # sell_indicator,
sell_crossed_indicator, # sell_crossed_indicator,
sell_real_num # sell_real_num
) # )
conditions.append(condition) # conditions.append(condition)
#
sell_indicator = self.sell_indicator2.value # sell_indicator = self.sell_indicator2.value
sell_crossed_indicator = self.sell_crossed_indicator2.value # sell_crossed_indicator = self.sell_crossed_indicator2.value
sell_operator = self.sell_operator2.value # sell_operator = self.sell_operator2.value
sell_real_num = self.sell_real_num2.value # sell_real_num = self.sell_real_num2.value
condition, dataframe = condition_generator( # condition, dataframe = condition_generator(
dataframe, # dataframe,
sell_operator, # sell_operator,
sell_indicator, # sell_indicator,
sell_crossed_indicator, # sell_crossed_indicator,
sell_real_num # sell_real_num
) # )
conditions.append(condition) # conditions.append(condition)
#
#
print(f"SELL indicators tested \n" # print(f"SELL indicators tested \n"
f"{self.sell_indicator0.value} {self.sell_crossed_indicator0.value} {self.sell_operator0.value} {self.sell_real_num0.value} \n" # f"{self.sell_indicator0.value} {self.sell_crossed_indicator0.value} {self.sell_operator0.value} {self.sell_real_num0.value} \n"
f"{self.sell_indicator1.value} {self.sell_crossed_indicator1.value} {self.sell_operator1.value} {self.sell_real_num1.value} \n" # f"{self.sell_indicator1.value} {self.sell_crossed_indicator1.value} {self.sell_operator1.value} {self.sell_real_num1.value} \n"
f"{self.sell_indicator2.value} {self.sell_crossed_indicator2.value} {self.sell_operator2.value} {self.sell_real_num2.value} \n" # f"{self.sell_indicator2.value} {self.sell_crossed_indicator2.value} {self.sell_operator2.value} {self.sell_real_num2.value} \n"
) # )
#
#
if conditions: # if conditions:
dataframe.loc[reduce(lambda x, y: x & y, conditions), ['exit_long', 'exit_tag']] = (1, 'god') # dataframe.loc[reduce(lambda x, y: x & y, conditions), ['exit_long', 'exit_tag']] = (1, 'god')
return dataframe return dataframe
def calculeDerivees( def calculeDerivees(
@@ -816,4 +936,50 @@ class Empty(IStrategy):
(momentum_short < momentum_long) (momentum_short < momentum_long)
) )
return dataframe return dataframe
@property
def protections(self):
return [
{
"method": "CooldownPeriod",
"stop_duration_candles": 12
},
# {
# "method": "MaxDrawdown",
# "lookback_period_candles": 96,
# "trade_limit": 4,
# "max_allowed_drawdown": 0.1,
# "stop_duration_candles": 24
# },
# {
# "method": "StoplossGuard",
# "lookback_period_candles": 96,
# "trade_limit": 2,
# "stop_duration_candles": 48,
# "only_per_pair": False
# },
# {
# "method": "LowProfitPairs",
# "lookback_period_candles": 6,
# "trade_limit": 2,
# "stop_duration_candles": 60,
# "required_profit": 0.02
# },
# {
# "method": "LowProfitPairs",
# "lookback_period_candles": 24,
# "trade_limit": 4,
# "stop_duration_candles": 2,
# "required_profit": 0.01
# }
]
def printLog(self, str):
if self.config.get('runmode') == 'hyperopt' or self.dp.runmode.value in ('hyperopt'):
return;
if not self.dp.runmode.value in ('backtest', 'hyperopt', 'lookahead-analysis'):
logger.info(str)
else:
if not self.dp.runmode.value in ('hyperopt'):
print(str)

126
Empty.txt Normal file
View File

@@ -0,0 +1,126 @@
BACKTESTING REPORT
┏━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Pair ┃ Trades ┃ Avg Profit % ┃ Tot Profit USDC ┃ Tot Profit % ┃ Avg Duration ┃ Win Draw Loss Win% ┃
┡━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━┩
│ BTC/USDC │ 45 │ 3.05 │ 903.533 │ 90.35 │ 15 days, 1:43:00 │ 44 0 1 97.8 │
│ TOTAL │ 45 │ 3.05 │ 903.533 │ 90.35 │ 15 days, 1:43:00 │ 44 0 1 97.8 │
└──────────┴────────┴──────────────┴─────────────────┴──────────────┴──────────────────┴────────────────────────┘
LEFT OPEN TRADES REPORT
┏━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Pair ┃ Trades ┃ Avg Profit % ┃ Tot Profit USDC ┃ Tot Profit % ┃ Avg Duration ┃ Win Draw Loss Win% ┃
┡━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━┩
│ BTC/USDC │ 1 │ -40.58 │ -1300.800 │ -130.08 │ 129 days, 4:00:00 │ 0 0 1 0 │
│ TOTAL │ 1 │ -40.58 │ -1300.800 │ -130.08 │ 129 days, 4:00:00 │ 0 0 1 0 │
└──────────┴────────┴──────────────┴─────────────────┴──────────────┴───────────────────┴────────────────────────┘
ENTER TAG STATS
┏━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Enter Tag ┃ Entries ┃ Avg Profit % ┃ Tot Profit USDC ┃ Tot Profit % ┃ Avg Duration ┃ Win Draw Loss Win% ┃
┡━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━┩
│ god │ 45 │ 3.05 │ 903.533 │ 90.35 │ 15 days, 1:43:00 │ 44 0 1 97.8 │
│ TOTAL │ 45 │ 3.05 │ 903.533 │ 90.35 │ 15 days, 1:43:00 │ 44 0 1 97.8 │
└───────────┴─────────┴──────────────┴─────────────────┴──────────────┴──────────────────┴────────────────────────┘
EXIT REASON STATS
┏━━━━━━━━━━━━━━━┳━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Exit Reason ┃ Exits ┃ Avg Profit % ┃ Tot Profit USDC ┃ Tot Profit % ┃ Avg Duration ┃ Win Draw Loss Win% ┃
┡━━━━━━━━━━━━━━━╇━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━┩
│ momentum_drop │ 44 │ 4.04 │ 2204.333 │ 220.43 │ 12 days, 11:29:00 │ 44 0 0 100 │
│ force_exit │ 1 │ -40.58 │ -1300.800 │ -130.08 │ 129 days, 4:00:00 │ 0 0 1 0 │
│ TOTAL │ 45 │ 3.05 │ 903.533 │ 90.35 │ 15 days, 1:43:00 │ 44 0 1 97.8 │
└───────────────┴───────┴──────────────┴─────────────────┴──────────────┴───────────────────┴────────────────────────┘
MIXED TAG STATS
┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Enter Tag ┃ Exit Reason ┃ Trades ┃ Avg Profit % ┃ Tot Profit USDC ┃ Tot Profit % ┃ Avg Duration ┃ Win Draw Loss Win% ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━┩
│ god │ momentum_drop │ 44 │ 4.04 │ 2204.333 │ 220.43 │ 12 days, 11:29:00 │ 44 0 0 100 │
│ god │ force_exit │ 1 │ -40.58 │ -1300.800 │ -130.08 │ 129 days, 4:00:00 │ 0 0 1 0 │
│ TOTAL │ │ 45 │ 3.05 │ 903.533 │ 90.35 │ 15 days, 1:43:00 │ 44 0 1 97.8 │
└───────────┴───────────────┴────────┴──────────────┴─────────────────┴──────────────┴───────────────────┴────────────────────────┘
MONTH BREAKDOWN
┏━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Month ┃ Trades ┃ Tot Profit USDC ┃ Profit Factor ┃ Win Draw Loss Win% ┃
┡━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━┩
│ 31/01/2024 │ 3 │ 101.297 │ 0.0 │ 3 0 0 100 │
│ 29/02/2024 │ 5 │ 123.805 │ 0.0 │ 5 0 0 100 │
│ 31/03/2024 │ 4 │ 119.449 │ 0.0 │ 4 0 0 100 │
│ 30/04/2024 │ 2 │ 48.352 │ 0.0 │ 2 0 0 100 │
│ 31/05/2024 │ 3 │ 102.779 │ 0.0 │ 3 0 0 100 │
│ 30/06/2024 │ 1 │ 40.932 │ 0.0 │ 1 0 0 100 │
│ 31/07/2024 │ 4 │ 163.017 │ 0.0 │ 4 0 0 100 │
│ 31/08/2024 │ 0 │ 0 │ 0.0 │ 0 0 0 0 │
│ 30/09/2024 │ 0 │ 0 │ 0.0 │ 0 0 0 0 │
│ 31/10/2024 │ 2 │ 84.227 │ 0.0 │ 2 0 0 100 │
│ 30/11/2024 │ 5 │ 301.166 │ 0.0 │ 5 0 0 100 │
│ 31/12/2024 │ 1 │ 96.283 │ 0.0 │ 1 0 0 100 │
│ 31/01/2025 │ 3 │ 160.102 │ 0.0 │ 3 0 0 100 │
│ 28/02/2025 │ 0 │ 0 │ 0.0 │ 0 0 0 0 │
│ 31/03/2025 │ 1 │ 62.274 │ 0.0 │ 1 0 0 100 │
│ 30/04/2025 │ 1 │ 192.722 │ 0.0 │ 1 0 0 100 │
│ 31/05/2025 │ 3 │ 138.491 │ 0.0 │ 3 0 0 100 │
│ 30/06/2025 │ 0 │ 0 │ 0.0 │ 0 0 0 0 │
│ 31/07/2025 │ 2 │ 210.269 │ 0.0 │ 2 0 0 100 │
│ 31/08/2025 │ 2 │ 72.441 │ 0.0 │ 2 0 0 100 │
│ 30/09/2025 │ 1 │ 61.018 │ 0.0 │ 1 0 0 100 │
│ 31/10/2025 │ 1 │ 125.709 │ 0.0 │ 1 0 0 100 │
│ 30/11/2025 │ 0 │ 0 │ 0.0 │ 0 0 0 0 │
│ 31/12/2025 │ 0 │ 0 │ 0.0 │ 0 0 0 0 │
│ 31/01/2026 │ 0 │ 0 │ 0.0 │ 0 0 0 0 │
│ 28/02/2026 │ 1 │ -1300.8 │ 0.0 │ 0 0 1 0 │
└────────────┴────────┴─────────────────┴───────────────┴────────────────────────┘
SUMMARY METRICS
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Metric ┃ Value ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ Backtesting from │ 2024-01-01 00:00:00 │
│ Backtesting to │ 2026-02-10 00:00:00 │
│ Trading Mode │ Spot │
│ Max open trades │ 1 │
│ │ │
│ Total/Daily Avg Trades │ 45 / 0.06 │
│ Starting balance │ 1000 USDC │
│ Final balance │ 1903.533 USDC │
│ Absolute profit │ 903.533 USDC │
│ Total profit % │ 90.35% │
│ CAGR % │ 35.63% │
│ Sortino │ -100.00 │
│ Sharpe │ 0.11 │
│ Calmar │ 5.52 │
│ SQN │ 0.66 │
│ Profit factor │ 1.69 │
│ Expectancy (Ratio) │ 20.08 (0.02) │
│ Avg. daily profit │ 1.172 USDC │
│ Avg. stake amount │ 1314.959 USDC │
│ Total trade volume │ 119488.585 USDC │
│ │ │
│ Best Pair │ BTC/USDC 90.35% │
│ Worst Pair │ BTC/USDC 90.35% │
│ Best trade │ BTC/USDC 9.64% │
│ Worst trade │ BTC/USDC -40.58% │
│ Best day │ 192.722 USDC │
│ Worst day │ -1300.8 USDC │
│ Days win/draw/lose │ 44 / 726 / 1 │
│ Min/Max/Avg. Duration Winners │ 0d 16:00 / 85d 14:00 / 12d 11:29 │
│ Min/Max/Avg. Duration Losers │ 129d 04:00 / 129d 04:00 / 129d 04:00 │
│ Max Consecutive Wins / Loss │ 44 / 1 │
│ Rejected Entry signals │ 0 │
│ Entry/Exit Timeouts │ 0 / 0 │
│ │ │
│ Min balance │ 1029.974 USDC │
│ Max balance │ 3204.333 USDC │
│ Max % of account underwater │ 40.60% │
│ Absolute drawdown │ 1300.8 USDC (40.60%) │
│ Drawdown duration │ 129 days 23:00:00 │
│ Profit at drawdown start │ 2204.333 USDC │
│ Profit at drawdown end │ 903.533 USDC │
│ Drawdown start │ 2025-10-03 01:00:00 │
│ Drawdown end │ 2026-02-10 00:00:00 │
│ Market change │ 64.37% │
└───────────────────────────────┴──────────────────────────────────────┘
Backtested 2024-01-01 00:00:00 -> 2026-02-10 00:00:00 | Max open trades : 1
STRATEGY SUMMARY
┏━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┓
┃ Strategy ┃ Trades ┃ Avg Profit % ┃ Tot Profit USDC ┃ Tot Profit % ┃ Avg Duration ┃ Win Draw Loss Win% ┃ Drawdown ┃
┡━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━┩
│ Empty │ 45 │ 3.05 │ 903.533 │ 90.35 │ 15 days, 1:43:00 │ 44 0 1 97.8 │ 1300.8 USDC 40.60% │
└──────────┴────────┴──────────────┴─────────────────┴──────────────┴──────────────────┴────────────────────────┴─────────────────────┘