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

┃ Strategy ┃ Trades ┃ Avg Profit % ┃ Tot Profit USDT ┃ Tot Profit % ┃     Avg Duration ┃  Win  Draw  Loss  Win% ┃             Drawdown ┃
┡━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━┩
│    Empty │     55 │         1.51 │         400.599 │        40.06 │ 13 days, 3:00:00 │   54     0     1  98.2 │ 648.572 USDT  31.65% │
└──────────┴────────┴──────────────┴─────────────────┴──────────────┴──────────────────┴────────────────────────┴──────────────────────┘
This commit is contained in:
Jérôme Delacotte
2026-02-24 20:40:34 +01:00
parent 91bf3ca8ae
commit 18c392b7f2
2 changed files with 126 additions and 55 deletions

View File

@@ -16,30 +16,22 @@
"max_open_trades": {
"max_open_trades": 20
},
"buy": {
"buy_crossed_indicator0": "sma24_deriv2",
"buy_crossed_indicator1": "sma48_deriv2",
"buy_crossed_indicator2": "sma60_deriv1",
"buy_indicator0": "sma12_deriv1",
"buy_indicator1": "sma3_score",
"buy_indicator2": "sma5_deriv2",
"buy_operator0": ">",
"buy_operator1": "D",
"buy_operator2": "D",
"buy_real_num0": -0.7,
"buy_real_num1": 1.0,
"buy_real_num2": -0.4
"protection": {
"drop_from_last_entry": -0.015,
"range_pos_stoploss": 0.1,
"stop_buying_indicator": "stop_buying48_1d",
"stoploss_force": -0.01,
"stoploss_indicator": "sma3_deriv2"
},
"sell": {
"sell_score_indicator": "sma48_score"
},
"protection": {
"range_pos_stoploss": 0.1,
"stop_buying_indicator": "stop_buying60_1d",
"stoploss_force": -0.01,
"stoploss_indicator": "sma3_deriv2"
"buy": {
"buy_deriv_sma12d": -0.01,
"buy_deriv_sma5d": -0.06,
"buy_deriv_sma60": -0.001
}
},
"ft_stratparam_v": 1,
"export_time": "2026-02-21 17:38:15.366827+00:00"
"export_time": "2026-02-23 21:02:56.692173+00:00"
}

151
Empty.py
View File

@@ -25,6 +25,7 @@ from random import shuffle
import logging
logger = logging.getLogger(__name__)
timeperiods = [3, 5, 12, 24, 36, 48, 60]
# Couleurs ANSI de base
RED = "\033[31m"
GREEN = "\033[32m"
@@ -34,9 +35,10 @@ MAGENTA = "\033[35m"
CYAN = "\033[36m"
RESET = "\033[0m"
timeperiods = [3, 5, 12, 24, 48, 60]
timeperiods = [3, 5, 12, 24, 36, 48, 60]
score_indicators = list()
stop_buying_indicators = list()
god_genes_with_timeperiod = list()
for timeperiod in timeperiods:
# god_genes_with_timeperiod.append(f'max{timeperiod}')
@@ -46,6 +48,10 @@ for timeperiod in timeperiods:
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}_score")
# stoploss_indicators.append(f"stop_buying{timeperiod}")
stop_buying_indicators.append(f"stop_buying{timeperiod}_1d")
score_indicators.append(f"sma{timeperiod}_score")
# score_indicators.append(f"sma{timeperiod}_score_1d")
@@ -317,7 +323,8 @@ class Empty(IStrategy):
'force_sell': False,
'force_buy': False,
'current_trade': None,
'last_trade': None
'last_trade': None,
'force_stop': False
}
for pair in ["BTC/USDC", "BTC/USDT"]
}
@@ -355,22 +362,26 @@ class Empty(IStrategy):
}
}
buy_deriv_sma60 = DecimalParameter(-0.005, 0.005, decimals=3, default=0, space='buy')
buy_deriv_sma5d = DecimalParameter(-0.07, 0.07, decimals=2, default=0, space='buy')
buy_deriv_sma12d = DecimalParameter(-0.07, 0.07, decimals=2, default=0, space='buy')
# Buy Hyperoptable Parameters/Spaces.
buy_crossed_indicator0 = CategoricalParameter(god_genes_with_timeperiod, default="ADD-20", space='buy')
buy_crossed_indicator1 = CategoricalParameter(god_genes_with_timeperiod, default="ASIN-6", space='buy')
buy_crossed_indicator2 = CategoricalParameter(god_genes_with_timeperiod, default="CDLEVENINGSTAR-50", space='buy')
buy_indicator0 = CategoricalParameter(god_genes_with_timeperiod, default="SMA-100", space='buy')
buy_indicator1 = CategoricalParameter(god_genes_with_timeperiod, default="WILLR-50", space='buy')
buy_indicator2 = CategoricalParameter(god_genes_with_timeperiod, default="CDLHANGINGMAN-20", space='buy')
buy_operator0 = CategoricalParameter(operators, default="/<R", space='buy')
buy_operator1 = CategoricalParameter(operators, default="<R", space='buy')
buy_operator2 = CategoricalParameter(operators, default="CB", space='buy')
buy_real_num0 = DecimalParameter(-1, 1, decimals=DECIMALS, default=0, space='buy')
buy_real_num1 = DecimalParameter(-1, 1, decimals=DECIMALS, default=0, space='buy')
buy_real_num2 = DecimalParameter(-1, 1, decimals=DECIMALS, default=0, space='buy')
# buy_crossed_indicator0 = CategoricalParameter(god_genes_with_timeperiod, default="ADD-20", space='buy')
# buy_crossed_indicator1 = CategoricalParameter(god_genes_with_timeperiod, default="ASIN-6", space='buy')
# buy_crossed_indicator2 = CategoricalParameter(god_genes_with_timeperiod, default="CDLEVENINGSTAR-50", space='buy')
#
# buy_indicator0 = CategoricalParameter(god_genes_with_timeperiod, default="SMA-100", space='buy')
# buy_indicator1 = CategoricalParameter(god_genes_with_timeperiod, default="WILLR-50", space='buy')
# buy_indicator2 = CategoricalParameter(god_genes_with_timeperiod, default="CDLHANGINGMAN-20", space='buy')
#
# buy_operator0 = CategoricalParameter(operators, default="/<R", space='buy')
# buy_operator1 = CategoricalParameter(operators, default="<R", space='buy')
# buy_operator2 = CategoricalParameter(operators, default="CB", space='buy')
#
# buy_real_num0 = DecimalParameter(-1, 1, decimals=DECIMALS, default=0, space='buy')
# buy_real_num1 = 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_crossed_indicator0 = CategoricalParameter(god_genes_with_timeperiod, default="CDLSHOOTINGSTAR-150", space='sell')
@@ -457,7 +468,7 @@ class Empty(IStrategy):
profit = trade.calc_profit(current_rate) #round(current_profit * trade.stake_amount, 1)
# last_lost = self.getLastLost(last_candle, pair)
# pct_first = 0
stake_amount = self.adjust_stake_amount(pair, last_candle)
stake_amount = min(self.wallets.get_available_stake_amount(), self.adjust_stake_amount(pair, last_candle))
# if (last_candle['enter_long'] == 1 and current_profit < -0.05 and stake_amount > 10) :
#
# print(f"adjust {current_time} {stake_amount}")
@@ -482,10 +493,11 @@ class Empty(IStrategy):
drop_from_last_entry = (current_rate - last_entry_price) / last_entry_price
if drop_from_last_entry <= self.drop_from_last_entry.value and last_candle['min60'] == last_candle_3['min60'] \
and (last_candle['range_pos'] < 0)\
and last_candle['hapercent'] > 0:
and last_candle['range_pos'] < 0.03 and last_candle['hapercent'] > 0:
# stake_amount = trade.stake_amount
self.pairs[trade.pair]['last_buy'] = current_rate
self.pairs[trade.pair]['total_amount'] += stake_amount
print(f"adjust {pair} drop={drop_from_last_entry} {current_time} dispo={dispo} amount={stake_amount} rate={current_rate}")
# print(f"adjust {pair} {current_time} dispo={dispo} amount={stake_amount} rate={current_rate}")
trade_type = last_candle['enter_tag'] if last_candle['enter_long'] == 1 else 'pct48'
@@ -495,7 +507,7 @@ class Empty(IStrategy):
last_candle=last_candle,
date=current_time,
action="🟧 Loss -",
dispo=dispo,
dispo=round(dispo,0),
pair=trade.pair,
rate=current_rate,
trade_type=trade_type,
@@ -525,6 +537,11 @@ class Empty(IStrategy):
condition = True
if condition and last_candle['range_pos'] > 0.05:# and self.pairs[pair]['force_stop']:
condition = last_candle['sma5'] > last_candle['sma60']
# if self.pairs[pair]['force_stop'] and last_candle['range_pos'] < 0.02:
# self.pairs[pair]['force_stop'] = False
if False and self.pairs[pair]['last_trade'].close_date is not None:
# base cooldown = n bougies / cooldown proportionnel au profit
@@ -639,12 +656,61 @@ class Empty(IStrategy):
before_last_candle = dataframe.iloc[-2]
self.pairs[pair]['current_trade'] = trade
momentum = last_candle[self.sell_score_indicator.value]
profit = trade.calc_profit(current_rate) #round(current_profit * trade.stake_amount, 1)
count_of_buys = trade.nr_of_successful_entries
expected_profit = self.expectedProfit(pair, last_candle)
self.pairs[pair]['max_profit'] = max(self.pairs[pair]['max_profit'], profit)
max_profit = self.pairs[pair]['max_profit']
baisse = 0
if profit > 0:
baisse = 1 - (profit / max_profit)
mx = max_profit / 5
self.pairs[pair]['count_of_buys'] = count_of_buys
self.pairs[pair]['current_profit'] = profit
dispo = round(self.wallets.get_available_stake_amount()) + self.pairs[pair]['total_amount']
self.log_trade(
last_candle=last_candle,
date=current_time,
action=("🔴 NOW" if self.pairs[pair]['stop'] else "🟢 NOW "),
dispo=dispo,
pair=pair,
rate=last_candle['close'],
trade_type='momentum' + str(round(momentum, 2)),
profit=round(profit, 2),
buys=count_of_buys,
stake=0
)
if profit > max(5, expected_profit) and baisse > 0.30:
self.pairs[pair]['force_sell'] = True
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'])
# if last_candle['range_pos'] > 0.05 and current_profit < - last_candle['range_pos'] /4 : #last_candle['cross_sma60']:
# self.pairs[pair]['force_sell'] = True
# return 'Range'
# if last_candle['range_pos'] > 0.05 and current_profit < - last_candle['range_pos'] /4 \
# and last_candle['sma5_deriv1_1d'] < 0 and last_candle['sma5_deriv2_1d'] < 0 \
# and last_candle['sma60_deriv1'] < 0 and last_candle['sma60_deriv2'] < 0:
# self.pairs[pair]['force_sell'] = True
# self.pairs[pair]['force_stop'] = True
# return 'Deriv';
if profit < - (dispo * 0.2):
print(f'dispo={dispo} wallet={round(self.wallets.get_available_stake_amount())} limit={-dispo * 0.1}')
self.pairs[pair]['force_sell'] = True
self.pairs[pair]['force_stop'] = True
return 'Force';
# if self.pairs[pair]['force_sell'] and self.pairs[pair]['last_trade'].close_profit > 0.02:
# return "Force"
if last_candle['stop_buying']:
self.pairs[pair]['force_sell'] = True
return 'Stop'
# if last_candle['stop_buying']:
# self.pairs[pair]['force_sell'] = True
# return 'Stop'
# Si momentum fort → on laisse courir
if momentum > 1:
return None
@@ -664,11 +730,10 @@ class Empty(IStrategy):
candle_at_buy = self.pairs[pair]['last_candle']
# if profit < - 100 :
# print("stop loss detected")
# self.pairs[pair]['force_sell'] = True
# return 0
# if candle_at_buy['range_pos'] > self.range_pos_stoploss.value and candle_at_buy[self.stoploss_indicator.value] < 0:
# return self.stoploss_force.value
# # print(f'test stop loss {self.stoploss} {last_candle["stop_buying12_1d"]}')
# 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"]
@@ -740,11 +805,11 @@ class Empty(IStrategy):
dataframe = merge_informative_pair(dataframe, informative, self.timeframe, "1d", ffill=True)
# ######################################################################################################
range_min = dataframe[f"min24_1d"]
range_min = dataframe[f"min12_1d"]
range_max = dataframe[f"max48"]
dataframe[f"range_pos"] = ((dataframe['mid'] - range_min) / (range_max)).rolling(5).mean()
dataframe['cross_sma60'] = qtpylib.crossed_above(dataframe['sma5'], dataframe['sma60'])
dataframe[f'stop_buying'] = qtpylib.crossed_below(dataframe[f"sma12"], dataframe['sma48'])
# récupérer le dernier trade fermé
@@ -812,15 +877,20 @@ class Empty(IStrategy):
# conditions.append(condition)
# conditions.append((dataframe[self.stop_buying_indicator.value] == False) | (dataframe['range_pos'] < 0))
#conditions.append((dataframe[self.stop_buying_indicator.value] == False) | (dataframe['range_pos'] < 0))
conditions.append(dataframe['sma60_deriv1'] > self.buy_deriv_sma60.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['hapercent'] > 0)
# # conditions.append(dataframe[f"range_pos"] <= 0.5)
conditions.append(((dataframe[f"range_pos"] < 0.05) ) | ((dataframe['sma12_deriv1'] > 0) & (dataframe['sma12_deriv2'] > 0)))
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_indicator1.value} {self.buy_crossed_indicator1.value} {self.buy_operator1.value} {self.buy_real_num1.value} \n"
f"{self.buy_indicator2.value} {self.buy_crossed_indicator2.value} {self.buy_operator2.value} {self.buy_real_num2.value} \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_indicator1.value} {self.buy_crossed_indicator1.value} {self.buy_operator1.value} {self.buy_real_num1.value} \n"
# f"{self.buy_indicator2.value} {self.buy_crossed_indicator2.value} {self.buy_operator2.value} {self.buy_real_num2.value} \n"
# )
if conditions:
dataframe.loc[
@@ -965,7 +1035,7 @@ class Empty(IStrategy):
return [
{
"method": "CooldownPeriod",
"stop_duration_candles": 12
"stop_duration_candles": 6
},
# {
# "method": "MaxDrawdown",
@@ -1123,3 +1193,12 @@ class Empty(IStrategy):
def getPctLastBuy(self, pair, last_candle):
return round((last_candle['close'] - self.pairs[pair]['last_buy']) / self.pairs[pair]['last_buy'], 4)
def expectedProfit(self, pair: str, last_candle: DataFrame):
lim = 0.005
pct = 0.001
pct_to_max = lim + pct * self.pairs[pair]['count_of_buys']
expected_profit = lim * self.pairs[pair]['total_amount'] # min(3 * lim, max(lim, pct_to_max)) # 0.004 + 0.002 * self.pairs[pair]['count_of_buys'] #min(0.01, first_max)
self.pairs[pair]['expected_profit'] = expected_profit
return expected_profit