From a228e5617227b3cd6b9e302ec713d0a76ff188f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Delacotte?= Date: Fri, 20 Feb 2026 21:54:17 +0100 Subject: [PATCH] =?UTF-8?q?=E2=94=8F=E2=94=81=E2=94=81=E2=94=81=E2=94=81?= =?UTF-8?q?=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=B3?= =?UTF-8?q?=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81?= =?UTF-8?q?=E2=94=81=E2=94=B3=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81?= =?UTF-8?q?=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81?= =?UTF-8?q?=E2=94=81=E2=94=81=E2=94=B3=E2=94=81=E2=94=81=E2=94=81=E2=94=81?= =?UTF-8?q?=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81?= =?UTF-8?q?=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=B3?= =?UTF-8?q?=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81?= =?UTF-8?q?=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81?= =?UTF-8?q?=E2=94=B3=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81?= =?UTF-8?q?=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81?= =?UTF-8?q?=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=B3=E2=94=81?= =?UTF-8?q?=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81?= =?UTF-8?q?=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81?= =?UTF-8?q?=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81?= =?UTF-8?q?=E2=94=81=E2=94=81=E2=94=B3=E2=94=81=E2=94=81=E2=94=81=E2=94=81?= =?UTF-8?q?=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81?= =?UTF-8?q?=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81?= =?UTF-8?q?=E2=94=81=E2=94=81=E2=94=81=E2=94=93=20=E2=94=83=20Strategy=20?= =?UTF-8?q?=E2=94=83=20Trades=20=E2=94=83=20Avg=20Profit=20%=20=E2=94=83?= =?UTF-8?q?=20Tot=20Profit=20USDC=20=E2=94=83=20Tot=20Profit=20%=20?= =?UTF-8?q?=E2=94=83=20=20=20=20=20Avg=20Duration=20=E2=94=83=20=20Win=20?= =?UTF-8?q?=20Draw=20=20Loss=20=20Win%=20=E2=94=83=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20Drawdown=20=E2=94=83=20=E2=94=A1=E2=94=81=E2=94=81?= =?UTF-8?q?=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81?= =?UTF-8?q?=E2=94=81=E2=95=87=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81?= =?UTF-8?q?=E2=94=81=E2=94=81=E2=94=81=E2=95=87=E2=94=81=E2=94=81=E2=94=81?= =?UTF-8?q?=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81?= =?UTF-8?q?=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=95=87=E2=94=81=E2=94=81?= =?UTF-8?q?=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81?= =?UTF-8?q?=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81?= =?UTF-8?q?=E2=94=81=E2=95=87=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81?= =?UTF-8?q?=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81?= =?UTF-8?q?=E2=94=81=E2=94=81=E2=95=87=E2=94=81=E2=94=81=E2=94=81=E2=94=81?= =?UTF-8?q?=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81?= =?UTF-8?q?=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81?= =?UTF-8?q?=E2=95=87=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81?= =?UTF-8?q?=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81?= =?UTF-8?q?=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81?= =?UTF-8?q?=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=95=87=E2=94=81=E2=94=81?= =?UTF-8?q?=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81?= =?UTF-8?q?=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81?= =?UTF-8?q?=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=81=E2=94=A9=20?= =?UTF-8?q?=E2=94=82=20=20=20=20Empty=20=E2=94=82=20=20=20=20=2045=20?= =?UTF-8?q?=E2=94=82=20=20=20=20=20=20=20=20=203.05=20=E2=94=82=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20903.533=20=E2=94=82=20=20=20=20=20=20=20=2090?= =?UTF-8?q?.35=20=E2=94=82=2015=20days,=201:43:00=20=E2=94=82=20=20=2044?= =?UTF-8?q?=20=20=20=20=200=20=20=20=20=201=20=2097.8=20=E2=94=82=201300.8?= =?UTF-8?q?=20USDC=20=2040.60%=20=E2=94=82=20=E2=94=94=E2=94=80=E2=94=80?= =?UTF-8?q?=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80?= =?UTF-8?q?=E2=94=80=E2=94=B4=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80?= =?UTF-8?q?=E2=94=80=E2=94=80=E2=94=80=E2=94=B4=E2=94=80=E2=94=80=E2=94=80?= =?UTF-8?q?=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80?= =?UTF-8?q?=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=B4=E2=94=80=E2=94=80?= =?UTF-8?q?=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80?= =?UTF-8?q?=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80?= =?UTF-8?q?=E2=94=80=E2=94=B4=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80?= =?UTF-8?q?=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80?= =?UTF-8?q?=E2=94=80=E2=94=80=E2=94=B4=E2=94=80=E2=94=80=E2=94=80=E2=94=80?= =?UTF-8?q?=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80?= =?UTF-8?q?=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80?= =?UTF-8?q?=E2=94=B4=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80?= =?UTF-8?q?=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80?= =?UTF-8?q?=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80?= =?UTF-8?q?=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=B4=E2=94=80=E2=94=80?= =?UTF-8?q?=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80?= =?UTF-8?q?=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80?= =?UTF-8?q?=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Empty.json | 38 ++--- Empty.py | 466 ++++++++++++++++++++++++++++++++++++----------------- Empty.txt | 126 +++++++++++++++ 3 files changed, 458 insertions(+), 172 deletions(-) create mode 100644 Empty.txt diff --git a/Empty.json b/Empty.json index 74cdd5d..9925e50 100644 --- a/Empty.json +++ b/Empty.json @@ -4,6 +4,15 @@ "roi": { "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": 20 }, @@ -21,30 +30,15 @@ "buy_real_num1": 1.0, "buy_real_num2": -0.4 }, + "protection": { + "stop_buying_indicator": "stop_buying12_1d", + "stoploss_indicator": "stop_buying12_1d", + "stoploss_timeperiod": "12" + }, "sell": { - "sell_crossed_indicator0": "sma24_deriv2", - "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 + "sell_score_indicator": "sma48_score" } }, "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" } \ No newline at end of file diff --git a/Empty.py b/Empty.py index 5e8c263..8f41af7 100644 --- a/Empty.py +++ b/Empty.py @@ -25,6 +25,8 @@ from random import shuffle timeperiods = [3, 5, 12, 24, 48, 60] +score_indicators = list() +stoploss_indicators = list() god_genes_with_timeperiod = list() for timeperiod in timeperiods: # 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}_deriv2") 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_down") # god_genes_with_timeperiod.append(f"sma{timeperiod}_trend_change_up") # god_genes_with_timeperiod.append(f"sma{timeperiod}_trend_change_down") +print(stoploss_indicators) + operators = [ "D", # Disabled gene ">", # Indicator, bigger than cross indicator @@ -249,14 +260,13 @@ class Empty(IStrategy): # Stoploss: stoploss = -1 - trailing_stop = True - trailing_stop_positive = 0.15 - trailing_stop_positive_offset = 0.20 - trailing_only_offset_is_reached = True + trailing_stop = False + # trailing_stop_positive = 0.15 + # trailing_stop_positive_offset = 0.20 + # trailing_only_offset_is_reached = False position_adjustment_enable = True - use_custom_stoploss = True - + use_custom_stoploss = False #max_open_trades = 3 @@ -295,6 +305,7 @@ class Empty(IStrategy): 'last_date': 0, 'stop': False, 'max_profit': 0, + 'last_profit': 0, 'total_amount': 0, 'has_gain': 0, 'force_sell': False, @@ -356,22 +367,27 @@ class Empty(IStrategy): 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') - 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_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_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=" 0): amount = min(self.wallets.get_available_stake_amount(), self.pairs[pair]['first_amount']) 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()) 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 - 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 print(f"adjust {current_time} {stake_amount}") 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_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) 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]['count_of_buys'] = 1 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_min'] = min(last_candle['close'], self.pairs[pair]['last_min']) self.pairs[pair]['last_date'] = current_time @@ -492,7 +536,7 @@ class Empty(IStrategy): stake_amount = self.adjust_stake_amount(pair, last_candle) self.pairs[pair]['first_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( # last_candle=last_candle, @@ -519,7 +563,7 @@ class Empty(IStrategy): profit = trade.calc_profit(rate) 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)) @@ -530,7 +574,7 @@ class Empty(IStrategy): profit = trade.calc_profit(rate) self.pairs[pair]['previous_profit'] = profit 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( # last_candle=last_candle, # date=current_time, @@ -541,6 +585,7 @@ class Empty(IStrategy): # dispo=dispo, # profit=round(profit, 2) # ) + self.pairs[pair]['first_amount'] = 0 self.pairs[pair]['force_sell'] = False self.pairs[pair]['has_gain'] = 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_trade'] = self.pairs[pair]['current_trade'] self.pairs[pair]['current_trade'] = None - # else: - # print(f"STOP triggered for {pair} ({exit_reason}) but condition blocked", "warning") + else: + 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 - def custom_exit(self, pair, trade, current_time, - current_rate, current_profit, **kwargs): + def custom_exit(self, pair, trade, current_time, current_rate, current_profit, **kwargs): 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 - momentum = last_row["sma24_score"] + momentum = last_candle[self.sell_score_indicator.value] # Si momentum fort → on laisse courir if momentum > 1: return None # 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 None - def custom_stoploss(self, pair, trade, current_time, - current_rate, current_profit, **kwargs): - - if current_profit < - 0.1 and self.wallets.get_available_stake_amount(): - return -0.1 - - return -1 + # def custom_stoploss(self, pair, trade, current_time, current_rate, current_profit, **kwargs): + # dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) + # last_candle = dataframe.iloc[-1] + # profit = trade.calc_profit(current_rate) + # + # # 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"] + # + # 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): + # 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: - + pair = metadata['pair'] dataframe = dataframe.copy() heikinashi = qtpylib.heikinashi(dataframe) dataframe['haopen'] = heikinashi['open'] @@ -602,19 +676,64 @@ class Empty(IStrategy): dataframe[f"sma{timeperiod}"] = dataframe['mid'].ewm(span=timeperiod, adjust=False).mean() self.calculeDerivees(dataframe, f"sma{timeperiod}", timeframe=self.timeframe, ema_period=timeperiod) - # ###################################################################################################### - dataframe['stop_buying_deb'] = (dataframe['sma60_trend_change_down'] == 1) - dataframe['stop_buying_end'] = (dataframe['sma60_trend_change_up'] == 1) - latched = np.zeros(len(dataframe), dtype=bool) + dataframe[f'stop_buying{timeperiod}_deb'] = (dataframe[f'sma{timeperiod}_trend_change_down'] == 1) + dataframe[f'stop_buying{timeperiod}_end'] = (dataframe[f'sma{timeperiod}_trend_change_up'] == 1) + latched = np.zeros(len(dataframe), dtype=bool) - for i in range(1, len(dataframe)): - if dataframe['stop_buying_deb'].iloc[i]: - latched[i] = True - elif dataframe['stop_buying_end'].iloc[i]: - latched[i] = False - else: - latched[i] = latched[i - 1] - dataframe['stop_buying'] = latched + for i in range(1, len(dataframe)): + if dataframe[f'stop_buying{timeperiod}_deb'].iloc[i]: + latched[i] = True + elif dataframe[f'stop_buying{timeperiod}_end'].iloc[i]: + latched[i] = False + else: + latched[i] = latched[i - 1] + 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 @@ -631,51 +750,52 @@ class Empty(IStrategy): 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 - 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['stop_buying'] == False)) - - # conditions.append((dataframe['min60'].shift(5) == dataframe['min60'])) - # conditions.append((dataframe['low'] < dataframe['min60'])) + # conditions.append(dataframe['hapercent'] > 0) + # # conditions.append(dataframe[f"range_pos"] <= 0.5) + # conditions.append(dataframe[f"sma5_deriv1"] > 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" @@ -692,57 +812,57 @@ class Empty(IStrategy): return dataframe def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: - conditions = list() - # TODO: Its not dry code! - sell_indicator = self.sell_indicator0.value - sell_crossed_indicator = self.sell_crossed_indicator0.value - sell_operator = self.sell_operator0.value - sell_real_num = self.sell_real_num0.value - condition, dataframe = condition_generator( - dataframe, - sell_operator, - sell_indicator, - sell_crossed_indicator, - sell_real_num - ) - conditions.append(condition) - - sell_indicator = self.sell_indicator1.value - sell_crossed_indicator = self.sell_crossed_indicator1.value - sell_operator = self.sell_operator1.value - sell_real_num = self.sell_real_num1.value - condition, dataframe = condition_generator( - dataframe, - sell_operator, - sell_indicator, - sell_crossed_indicator, - sell_real_num - ) - conditions.append(condition) - - sell_indicator = self.sell_indicator2.value - sell_crossed_indicator = self.sell_crossed_indicator2.value - sell_operator = self.sell_operator2.value - sell_real_num = self.sell_real_num2.value - condition, dataframe = condition_generator( - dataframe, - sell_operator, - sell_indicator, - sell_crossed_indicator, - sell_real_num - ) - conditions.append(condition) - - - 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_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" - ) - - - if conditions: - dataframe.loc[reduce(lambda x, y: x & y, conditions), ['exit_long', 'exit_tag']] = (1, 'god') + # conditions = list() + # # TODO: Its not dry code! + # sell_indicator = self.sell_indicator0.value + # sell_crossed_indicator = self.sell_crossed_indicator0.value + # sell_operator = self.sell_operator0.value + # sell_real_num = self.sell_real_num0.value + # condition, dataframe = condition_generator( + # dataframe, + # sell_operator, + # sell_indicator, + # sell_crossed_indicator, + # sell_real_num + # ) + # conditions.append(condition) + # + # sell_indicator = self.sell_indicator1.value + # sell_crossed_indicator = self.sell_crossed_indicator1.value + # sell_operator = self.sell_operator1.value + # sell_real_num = self.sell_real_num1.value + # condition, dataframe = condition_generator( + # dataframe, + # sell_operator, + # sell_indicator, + # sell_crossed_indicator, + # sell_real_num + # ) + # conditions.append(condition) + # + # sell_indicator = self.sell_indicator2.value + # sell_crossed_indicator = self.sell_crossed_indicator2.value + # sell_operator = self.sell_operator2.value + # sell_real_num = self.sell_real_num2.value + # condition, dataframe = condition_generator( + # dataframe, + # sell_operator, + # sell_indicator, + # sell_crossed_indicator, + # sell_real_num + # ) + # conditions.append(condition) + # + # + # 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_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" + # ) + # + # + # if conditions: + # dataframe.loc[reduce(lambda x, y: x & y, conditions), ['exit_long', 'exit_tag']] = (1, 'god') return dataframe def calculeDerivees( @@ -816,4 +936,50 @@ class Empty(IStrategy): (momentum_short < momentum_long) ) - return dataframe \ No newline at end of file + 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) \ No newline at end of file diff --git a/Empty.txt b/Empty.txt new file mode 100644 index 0000000..1e31e5d --- /dev/null +++ b/Empty.txt @@ -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% │ +└──────────┴────────┴──────────────┴─────────────────┴──────────────┴──────────────────┴────────────────────────┴─────────────────────┘