Backtested 2023-01-01 00:00:00 -> 2026-02-20 00:00:00 | Max open trades : 1

┏━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┓
┃ Strategy ┃ Trades ┃ Avg Profit % ┃ Tot Profit USDT ┃ Tot Profit % ┃     Avg Duration ┃  Win  Draw  Loss  Win% ┃            Drawdown ┃
┡━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━┩
│    Empty │     85 │         1.06 │        1239.222 │       123.92 │ 2 days, 12:39:00 │   54     0    31  63.5 │ 206.862 USDT  8.61% │
└──────────┴────────┴──────────────┴─────────────────┴──────────────┴──────────────────┴────────────────────────┴─────────────────────┘
This commit is contained in:
Jérôme Delacotte
2026-02-25 11:21:57 +01:00
parent 18c392b7f2
commit 63b425aaf6

View File

@@ -324,7 +324,8 @@ class Empty(IStrategy):
'force_buy': False, 'force_buy': False,
'current_trade': None, 'current_trade': None,
'last_trade': None, 'last_trade': None,
'force_stop': False 'force_stop': False,
'count_of_lost': 0
} }
for pair in ["BTC/USDC", "BTC/USDT"] for pair in ["BTC/USDC", "BTC/USDT"]
} }
@@ -432,7 +433,7 @@ class Empty(IStrategy):
sl_max = self.wallets.get_available_stake_amount() / 2 sl_max = self.wallets.get_available_stake_amount() / 2
amount = sl_min + (1 - range_pos) * (sl_max - sl_min) amount = sl_min + (1 - range_pos) * (sl_max - sl_min)
# amount = self.wallets.get_available_stake_amount() / 8 amount = self.wallets.get_available_stake_amount()
return min(amount, self.wallets.get_available_stake_amount()) return min(amount, self.wallets.get_available_stake_amount())
@@ -535,7 +536,9 @@ 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 condition = False if self.pairs[pair]['count_of_lost'] >= 1 \
and (last_candle['sma12_deriv1_1d'] < 0.001 \
or last_candle['sma12_deriv2_1d'] < 0.001) else True
if condition and last_candle['range_pos'] > 0.05:# and self.pairs[pair]['force_stop']: if condition and last_candle['range_pos'] > 0.05:# and self.pairs[pair]['force_stop']:
condition = last_candle['sma5'] > last_candle['sma60'] condition = last_candle['sma5'] > last_candle['sma60']
@@ -619,7 +622,6 @@ class Empty(IStrategy):
if allow_to_sell: if allow_to_sell:
self.pairs[pair]['last_sell'] = rate self.pairs[pair]['last_sell'] = rate
self.pairs[pair]['last_candle'] = last_candle self.pairs[pair]['last_candle'] = last_candle
self.pairs[pair]['max_profit'] = 0
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())
@@ -645,6 +647,12 @@ class Empty(IStrategy):
self.pairs[pair]['last_date'] = current_time self.pairs[pair]['last_date'] = current_time
self.pairs[pair]['last_trade'] = trade self.pairs[pair]['last_trade'] = trade
self.pairs[pair]['current_trade'] = None self.pairs[pair]['current_trade'] = None
self.pairs[pair]['max_profit'] = 0
if profit < 0:
self.pairs[pair]['count_of_lost'] += 1
else:
self.pairs[pair]['count_of_lost'] = 0
else: else:
print(f"{current_time} 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
@@ -656,12 +664,14 @@ class Empty(IStrategy):
before_last_candle = dataframe.iloc[-2] before_last_candle = dataframe.iloc[-2]
self.pairs[pair]['current_trade'] = trade self.pairs[pair]['current_trade'] = trade
momentum = last_candle[self.sell_score_indicator.value] momentum = last_candle[self.sell_score_indicator.value]
profit = trade.calc_profit(current_rate) #round(current_profit * trade.stake_amount, 1) profit = trade.calc_profit(current_rate) #round(current_profit * trade.stake_amount, 1)
count_of_buys = trade.nr_of_successful_entries count_of_buys = trade.nr_of_successful_entries
expected_profit = self.expectedProfit(pair, last_candle) expected_profit = self.expectedProfit(pair, last_candle)
self.pairs[pair]['max_profit'] = max(self.pairs[pair]['max_profit'], profit) self.pairs[pair]['max_profit'] = max(self.pairs[pair]['max_profit'], profit)
max_profit = self.pairs[pair]['max_profit'] max_profit = self.pairs[pair]['max_profit']
baisse = 0 baisse = 0
if profit > 0: if profit > 0:
baisse = 1 - (profit / max_profit) baisse = 1 - (profit / max_profit)
@@ -669,22 +679,27 @@ class Empty(IStrategy):
self.pairs[pair]['count_of_buys'] = count_of_buys self.pairs[pair]['count_of_buys'] = count_of_buys
self.pairs[pair]['current_profit'] = profit self.pairs[pair]['current_profit'] = profit
dispo = round(self.wallets.get_available_stake_amount()) + self.pairs[pair]['total_amount'] dispo = round(self.wallets.get_available_stake_amount() + self.pairs[pair]['total_amount'])
self.log_trade( # self.log_trade(
last_candle=last_candle, # last_candle=last_candle,
date=current_time, # date=current_time,
action=("🔴 NOW" if self.pairs[pair]['stop'] else "🟢 NOW "), # action=("🔴 NOW" if self.pairs[pair]['stop'] else "🟢 NOW "),
dispo=dispo, # dispo=dispo,
pair=pair, # pair=pair,
rate=last_candle['close'], # rate=last_candle['close'],
trade_type='momentum' + str(round(momentum, 2)), # trade_type='momentum' + str(round(momentum, 2)),
profit=round(profit, 2), # profit=round(profit, 2),
buys=count_of_buys, # buys=count_of_buys,
stake=0 # stake=0
) # )
if profit > max(5, expected_profit) and baisse > 0.30: if current_profit < - 0.02 and last_candle[f"close"] <= last_candle['sma60']:
self.pairs[pair]['force_sell'] = True
return 'sma60'
if profit > max(5, expected_profit) and baisse > 0.30 and last_candle[f"close"] <= last_candle['sma5']\
and last_candle['percent3'] < 0 and last_candle['percent5'] < 0:
self.pairs[pair]['force_sell'] = True self.pairs[pair]['force_sell'] = True
self.pairs[pair]['force_buy'] = (self.pairs[pair]['count_of_buys'] - self.pairs[pair]['has_gain'] > 3) self.pairs[pair]['force_buy'] = (self.pairs[pair]['count_of_buys'] - self.pairs[pair]['has_gain'] > 3)
return str(count_of_buys) + '_' + 'B30_' + pair + '_' + str(self.pairs[pair]['has_gain']) return str(count_of_buys) + '_' + 'B30_' + pair + '_' + str(self.pairs[pair]['has_gain'])
@@ -712,14 +727,15 @@ class Empty(IStrategy):
# self.pairs[pair]['force_sell'] = True # self.pairs[pair]['force_sell'] = True
# return 'Stop' # return 'Stop'
# 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 and last_candle[self.sell_score_indicator.value] < before_last_candle[self.sell_score_indicator.value]\ # 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']: # and last_candle['close'] < last_candle['sma5']:
self.pairs[pair]['last_profit'] = current_profit # self.pairs[pair]['last_profit'] = current_profit
return "momentum_drop" # return "momentum_drop"
return None return None
@@ -801,6 +817,9 @@ class Empty(IStrategy):
informative[f"sma{timeperiod}"] = informative['mid'].ewm(span=timeperiod, adjust=False).mean() informative[f"sma{timeperiod}"] = informative['mid'].ewm(span=timeperiod, adjust=False).mean()
self.calculeDerivees(informative, f"sma{timeperiod}", timeframe=self.timeframe, ema_period=timeperiod) self.calculeDerivees(informative, f"sma{timeperiod}", timeframe=self.timeframe, ema_period=timeperiod)
informative['rsi'] = talib.RSI(informative['close'], timeperiod=14)
informative['max_rsi_12'] = talib.MAX(informative['rsi'], timeperiod=12)
informative['max_rsi_24'] = talib.MAX(informative['rsi'], timeperiod=24)
dataframe = merge_informative_pair(dataframe, informative, self.timeframe, "1d", ffill=True) dataframe = merge_informative_pair(dataframe, informative, self.timeframe, "1d", ffill=True)
# ###################################################################################################### # ######################################################################################################
@@ -809,9 +828,13 @@ class Empty(IStrategy):
range_max = dataframe[f"max48"] range_max = dataframe[f"max48"]
dataframe[f"range_pos"] = ((dataframe['mid'] - range_min) / (range_max)).rolling(5).mean() dataframe[f"range_pos"] = ((dataframe['mid'] - range_min) / (range_max)).rolling(5).mean()
dataframe['cross_sma60'] = qtpylib.crossed_above(dataframe['sma5'], dataframe['sma60']) dataframe['cross_sma60'] = qtpylib.crossed_below(dataframe['sma12'], dataframe['sma5_1d'])
dataframe[f'stop_buying'] = qtpylib.crossed_below(dataframe[f"sma12"], dataframe['sma48']) dataframe[f'stop_buying'] = qtpylib.crossed_below(dataframe[f"sma12"], dataframe['sma48'])
dataframe['rsi'] = talib.RSI(dataframe['close'], timeperiod=14)
dataframe['max_rsi_12'] = talib.MAX(dataframe['rsi'], timeperiod=12)
dataframe['max_rsi_24'] = talib.MAX(dataframe['rsi'], timeperiod=24)
# récupérer le dernier trade fermé # récupérer le dernier trade fermé
trades = Trade.get_trades_proxy(pair=pair,is_open=False) trades = Trade.get_trades_proxy(pair=pair,is_open=False)
if trades: if trades:
@@ -883,7 +906,10 @@ class Empty(IStrategy):
conditions.append(dataframe['sma5_deriv1_1d'] > self.buy_deriv_sma5d.value) conditions.append(dataframe['sma5_deriv1_1d'] > self.buy_deriv_sma5d.value)
conditions.append(dataframe['sma12_deriv1_1d'] > self.buy_deriv_sma12d.value) conditions.append(dataframe['sma12_deriv1_1d'] > self.buy_deriv_sma12d.value)
conditions.append(dataframe['hapercent'] > 0) conditions.append(dataframe['hapercent'] > 0)
# # conditions.append(dataframe[f"range_pos"] <= 0.5) conditions.append(dataframe['max_rsi_24'] < 80)
conditions.append(dataframe['max_rsi_12_1d'] < 65)
conditions.append(dataframe[f"close"] > dataframe['sma60'])
conditions.append(((dataframe[f"range_pos"] < 0.05) ) | ((dataframe['sma12_deriv1'] > 0) & (dataframe['sma12_deriv2'] > 0))) conditions.append(((dataframe[f"range_pos"] < 0.05) ) | ((dataframe['sma12_deriv1'] > 0) & (dataframe['sma12_deriv2'] > 0)))
# print(f"BUY indicators tested \n" # print(f"BUY indicators tested \n"
@@ -1076,7 +1102,7 @@ class Empty(IStrategy):
self.printLog( self.printLog(
f"| {'Date':<16} | {'Action':<10} |{'Pair':<5}| {'Trade Type':<18} |{'Rate':>8} | {'Dispo':>6} | {'Profit':>8} " f"| {'Date':<16} | {'Action':<10} |{'Pair':<5}| {'Trade Type':<18} |{'Rate':>8} | {'Dispo':>6} | {'Profit':>8} "
f"| {'Pct':>6} | {'max_touch':>11} | {'last_lost':>12} | {'last_max':>7}| {'last_max':>7}|{'Buys':>5}| {'Stake':>5} |" f"| {'Pct':>6} | {'max_touch':>11} | {'last_lost':>12} | {'last_max':>7}| {'last_max':>7}|{'Buys':>5}| {'Stake':>5} |"
f"{'rsi':>6}|Distmax|s201d|s5_1d|s5_2d|s51h|s52h|smt1h|smt2h|tdc1d|tdc1h" f"{'rsi':>6}|"
) )
self.printLineLog() self.printLineLog()
df = pd.DataFrame.from_dict(self.pairs, orient='index') df = pd.DataFrame.from_dict(self.pairs, orient='index')
@@ -1150,7 +1176,7 @@ class Empty(IStrategy):
last_min = int(self.pairs[pair]['last_min']) if self.pairs[pair]['last_min'] > 1 else round( last_min = int(self.pairs[pair]['last_min']) if self.pairs[pair]['last_min'] > 1 else round(
self.pairs[pair]['last_min'], 3) self.pairs[pair]['last_min'], 3)
profit = str(profit) + '/' + str(round(self.pairs[pair]['max_profit'], 2)) profit = str(round(profit, 2)) + '/' + str(round(self.pairs[pair]['max_profit'], 2))
# 🟢 Dérivée 1 > 0 et dérivée 2 > 0: tendance haussière qui saccélère. # 🟢 Dérivée 1 > 0 et dérivée 2 > 0: tendance haussière qui saccélère.
# 🟡 Dérivée 1 > 0 et dérivée 2 < 0: tendance haussière qui ralentit → essoufflement potentiel. # 🟡 Dérivée 1 > 0 et dérivée 2 < 0: tendance haussière qui ralentit → essoufflement potentiel.