diff --git a/FrictradeLearning.py b/FrictradeLearning.py index f8d0133..20214f4 100644 --- a/FrictradeLearning.py +++ b/FrictradeLearning.py @@ -22,7 +22,6 @@ import joblib import matplotlib.pyplot as plt import mpmath as mp import numpy as np -import optuna import pandas as pd import seaborn as sns import shap @@ -31,6 +30,7 @@ import ta import talib.abstract as talib from freqtrade.persistence import Trade from freqtrade.strategy import (CategoricalParameter, DecimalParameter, IntParameter, IStrategy, merge_informative_pair) +import optuna from optuna.visualization import plot_optimization_history from optuna.visualization import plot_parallel_coordinate from optuna.visualization import plot_param_importances @@ -988,7 +988,7 @@ class FrictradeLearning(IStrategy): count = 0 pct = 0 dataframe = dataframe.copy() - total_stake = 0 + total_stake = 1 loss_amount = 0 dca_previous = 0 for dca in self.pairs[pair]['dca_thresholds']: diff --git a/Zeus_8_3_2_B_4_2.json b/Zeus_8_3_2_B_4_2.json index bb5fd35..843c2c6 100644 --- a/Zeus_8_3_2_B_4_2.json +++ b/Zeus_8_3_2_B_4_2.json @@ -7,40 +7,30 @@ "stoploss": { "stoploss": -1.0 }, - "max_open_trades": { - "max_open_trades": 80 - }, - "buy": { - "mises": 5, - "deriv_5m_slope_sup_buy": 0.14, - "indic_5m_slope_inf_buy": "sma12", - "indic_5m_slope_sup_buy": "sma12", - "indic_deriv_5m_slop_sup_buy": "sma5", - "mise_factor_buy": 0.01, - "pct": 0.029, - "pct_inc": 0.0027 - }, - "sell": { - "deriv_5m_slope_inf_sell": 0.45, - "deriv_5m_slope_sup_sell": -0.07, - "indic_5m_slope_inf_sell": "sma60", - "indic_5m_slope_sup_sell": "sma12", - "indic_deriv_5m_slope_inf_sell": "sma5", - "indic_deriv_5m_slope_sup_sell": "sma24" - }, - "protection": { - "deriv1_buy_protect": 0.06, - "indic_1h_slope_sup": "sma5", - "indic_5m_slope_sup": "sma24", - "rsi_buy_protect": 53 - }, "trailing": { "trailing_stop": true, "trailing_stop_positive": 0.148, "trailing_stop_positive_offset": 0.218, "trailing_only_offset_is_reached": false + }, + "max_open_trades": { + "max_open_trades": 20 + }, + "buy": { + "mise_factor_buy": 0.01, + "pct": 0.029, + "pct_inc": 0.0027, + "bm1": 800, + "bm2": 200, + "bm3": 0, + "bm4": 800, + "bp0": 0, + "bp1": 0, + "bp2": 800, + "bp3": 0, + "bp4": 0 } }, "ft_stratparam_v": 1, - "export_time": "2025-11-06 13:54:42.462181+00:00" + "export_time": "2026-02-15 14:23:04.601092+00:00" } \ No newline at end of file diff --git a/Zeus_8_3_2_B_4_2.py b/Zeus_8_3_2_B_4_2.py index 86cd267..75ab2bc 100644 --- a/Zeus_8_3_2_B_4_2.py +++ b/Zeus_8_3_2_B_4_2.py @@ -62,6 +62,34 @@ from tabulate import tabulate from sklearn.model_selection import GridSearchCV from sklearn.feature_selection import VarianceThreshold import seaborn as sns +import optuna +import shap +from optuna.visualization import plot_optimization_history +from optuna.visualization import plot_parallel_coordinate +from optuna.visualization import plot_param_importances +from optuna.visualization import plot_slice +from pandas import DataFrame +from sklearn.calibration import CalibratedClassifierCV +from sklearn.feature_selection import SelectFromModel +from sklearn.feature_selection import VarianceThreshold +from sklearn.inspection import PartialDependenceDisplay +from sklearn.inspection import permutation_importance +from sklearn.linear_model import LogisticRegression +from sklearn.metrics import brier_score_loss, roc_auc_score +from sklearn.metrics import ( + classification_report, + confusion_matrix, + accuracy_score, + roc_curve, + precision_score, recall_score +) +from sklearn.metrics import f1_score +from sklearn.model_selection import train_test_split +from sklearn.pipeline import Pipeline +from sklearn.preprocessing import StandardScaler +from sklearn.tree import export_text +from xgboost import XGBClassifier + # Couleurs ANSI de base RED = "\033[31m" @@ -81,47 +109,17 @@ def normalize(df): df = (df - df.min()) / (df.max() - df.min()) return df +def generate_state_params(states, mises): + params = {} + for s in states: + prefix = "bm" if s < 0 else "bp" + name = f"{prefix}{abs(s)}" + params[name] = CategoricalParameter(mises, default=200, space='buy', optimize=True, load=True) + return params class Zeus_8_3_2_B_4_2(IStrategy): # Machine Learning - # model_indicators = [ - # 'rsi', 'rsi_deriv1', 'rsi_deriv2', "max_rsi_12", - # "bb_percent", - # 'vol_24', - # 'percent3', - # 'sma5_dist', 'sma5_deriv1', 'sma5_deriv2', - # 'sma24_dist', 'sma24_deriv1', 'sma24_deriv2', - # 'sma60_dist', 'sma60_deriv1', 'sma60_deriv2', - # 'down_pct', 'slope_norm', - # 'min_max_60', - # 'rsi_slope', 'adx_change', 'volatility_ratio', - # 'slope_ratio', 'bb_width', - # 'rsi_1h', 'rsi_deriv1_1h', 'rsi_deriv2_1h', "max_rsi_12_1h", - # ] - - model_indicators = [ - # 'hapercent', - # 'percent', 'percent3', 'percent12', 'percent24', - # 'sma5_dist', 'sma5_deriv1', 'sma5_deriv2', - # 'sma12_dist', 'sma12_deriv1', 'sma12_deriv2', - # 'sma24_dist', 'sma24_deriv1', 'sma24_deriv2', - # 'sma48_dist', 'sma48_deriv1', 'sma48_deriv2', - # 'sma60_dist', 'sma60_deriv1', 'sma60_deriv2', - # 'mid_smooth_3_deriv1', 'mid_smooth_3_deriv2', - # 'mid_smooth_5_dist', 'mid_smooth_5_deriv1', 'mid_smooth_5_deriv2', - # 'mid_smooth_12_dist', 'mid_smooth_12_deriv1', 'mid_smooth_12_deriv2', - # 'mid_smooth_24_dist', 'mid_smooth_24_deriv1', 'mid_smooth_24_deriv2', - # 'rsi', 'max_rsi_12', 'max_rsi_24', 'rsi_dist', - # 'rsi_deriv1', 'rsi_deriv2', 'min_max_60', - # 'bb_percent', 'bb_width', 'macd', - # 'macdsignal', 'macdhist', 'slope', 'slope_smooth', 'atr', 'atr_norm', - # 'adx', - # 'obv', 'obv_deriv1', 'obv_deriv2', - # 'obv5', 'obv5_deriv1', 'obv5_deriv2', - # 'vol_24', 'down_count', 'up_count', 'down_pct', 'up_pct', - # 'rsi_slope', 'adx_change', 'volatility_ratio', 'rsi_diff', 'slope_ratio', 'volume_sma_deriv', - # 'volume_dist', 'volume_deriv1', 'volume_deriv2', 'slope_norm', - ] + model_indicators = [ ] levels = [1, 2, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20] startup_candle_count = 12 * 24 * 5 @@ -302,35 +300,19 @@ class Zeus_8_3_2_B_4_2(IStrategy): trades = list() max_profit_pairs = {} - # # sma24_deriv1_1d_stop_protection = DecimalParameter(-0.2, 0.2, default=0.05, decimals=2, space='protection', - # # optimize=True, load=True) - # sma5_deriv1_1d_stop_protection = DecimalParameter(-5, 0, default=0.5, decimals=1, space='protection', - # optimize=True, load=True) - # sma5_deriv2_1d_stop_protection = DecimalParameter(-5, 0, default=0.5, decimals=1, space='protection', optimize=True, - # load=True) - # - # # sma24_deriv1_1d_start_protection = DecimalParameter(-0.2, 0.2, default=0.05, decimals=2, space='protection', - # # optimize=True, load=True) - # sma5_deriv1_1d_restart_protection = DecimalParameter(0, 5, default=0.5, decimals=1, space='protection', - # optimize=True, load=True) - # sma5_deriv2_1d_restart_protection = DecimalParameter(0, 5, default=0.5, decimals=1, space='protection', - # optimize=True, - # load=True) - # - mise_factor_buy = DecimalParameter(0.01, 0.1, default=0.05, decimals=2, space='buy', optimize=True, load=True) + mise_factor_buy = DecimalParameter(0.01, 0.1, default=0.05, decimals=2, space='buy', optimize=False, load=True) indicators = {'sma5', 'sma12', 'sma24', 'sma60'} - indicators_percent = {'percent', 'percent3', 'percent12', 'percent24', 'percent_1h', 'percent3_1h', 'percent12_1h', 'percent24_1h'} + indicators_percent = {'percent', 'percent5', 'percent12', 'percent24', 'percent_1h', 'percent5_1h', 'percent12_1h', 'percent24_1h'} - mises = IntParameter(1, 50, default=5, space='buy', optimize=False, load=False) + pct = DecimalParameter(0.005, 0.05, default=0.012, decimals=3, space='buy', optimize=False, load=True) + pct_inc = DecimalParameter(0.0001, 0.003, default=0.0022, decimals=4, space='buy', optimize=False, load=True) - pct = DecimalParameter(0.005, 0.05, default=0.012, decimals=3, space='buy', optimize=True, load=True) - pct_inc = DecimalParameter(0.0001, 0.003, default=0.0022, decimals=4, space='buy', optimize=True, load=True) - deriv1_buy_protect = DecimalParameter(-0.3, 0.1, default=-0.1, decimals=2, space='protection', optimize=True, load=True) - rsi_buy_protect = IntParameter(50, 90, default=70, space='protection', optimize=True, load=True) - indic_5m_slope_sup = CategoricalParameter(indicators, default="sma60", space='protection') - indic_1h_slope_sup = CategoricalParameter(indicators, default="sma5", space='protection') + + mises = [0,200,400,600,800,1000] + states = [-4, -3, -2, -1, 0, 1, 2, 3, 4] + locals().update(generate_state_params(states, mises)) labels = ['B3', 'B2', 'B1', 'N0', 'H1', 'H2', 'H3'] index_labels = ['B3', 'B2', 'B1', 'N0', 'H1', 'H2', 'H3'] @@ -357,17 +339,6 @@ class Zeus_8_3_2_B_4_2(IStrategy): sma5_derive1_2_numeric_matrice = sma5_derive1_2_matrice_df.reindex(index=ordered_labels, columns=ordered_labels).values - # paliers = {} - - # ========================================================================= - # Parameters hyperopt - - # buy_mid_smooth_3_deriv1 = DecimalParameter(-0.1, 0.1, decimals=2, default=-0.06, space='buy') - # buy_mid_smooth_24_deriv1 = DecimalParameter(-0.6, 0, decimals=2, default=-0.03, space='buy') - # buy_horizon_predict_1h = IntParameter(1, 6, default=2, space='buy') - - # buy_level_predict_1h = IntParameter(2, 5, default=4, space='buy') - should_enter_trade_count = 0 def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float, time_in_force: str, @@ -381,19 +352,12 @@ class Zeus_8_3_2_B_4_2(IStrategy): last_candle = dataframe.iloc[-1].squeeze() last_candle_2 = dataframe.iloc[-2].squeeze() last_candle_3 = dataframe.iloc[-3].squeeze() - # val = self.getProbaHausse144(last_candle) - - # trend = last_candle['trend_class'] - # params = self.loadParamsFor(pair, trend) - - # indic_5m = self.getParamValue(pair, trend, 'buy', 'indic_5m') - # indic_deriv1_5m = self.getParamValue( pair, trend, 'buy', 'indic_deriv1_5m') - # indic_deriv2_5m = self.getParamValue( pair, trend, 'buy', 'indic_deriv2_5m') condition = True #(last_candle[f"{indic_5m}_deriv1"] >= indic_deriv1_5m) and (last_candle[f"{indic_5m}_deriv2"] >= indic_deriv2_5m) # allow_to_buy = True #(not self.stop_all) #& (not self.all_down) # and val > self.buy_val.value #not last_candle['tendency'] in (-1, -2) # (rate <= float(limit)) | (entry_tag == 'force_entry') + self.should_enter_trade(pair, last_candle, current_time) allow_to_buy = (condition and not self.pairs[pair]['stop']) | (entry_tag == 'force_entry') # if allow_to_buy: @@ -413,17 +377,17 @@ class Zeus_8_3_2_B_4_2(IStrategy): if not self.should_enter_trade(pair, last_candle, current_time): allow_to_buy = False - # On récupère le dernier trade ouvert (toutes paires) - last_date = self.pairs[pair]['last_date'] - - if not last_date: - last_date = dataframe.iloc[-1]["date"] - self.pairs[pair]['last_date'] = last_date - - now = dataframe.iloc[-1]["date"] - - if now - last_date >= timedelta(hours=24): - allow_to_buy = True + # # On récupère le dernier trade ouvert (toutes paires) + # last_date = self.pairs[pair]['last_date'] + # + # if not last_date: + # last_date = dataframe.iloc[-1]["date"] + # self.pairs[pair]['last_date'] = last_date + # + # now = dataframe.iloc[-1]["date"] + # + # if now - last_date >= timedelta(hours=24): + # allow_to_buy = True if allow_to_buy: self.trades = list() @@ -557,7 +521,7 @@ class Zeus_8_3_2_B_4_2(IStrategy): self.log_trade( last_candle=last_candle, date=current_time, - action="🔴 CURRENT" if self.pairs[pair]['stop'] else "🟢 CURRENT " + str(hours_since_first_buy), + action=("🔴 NOW" if self.pairs[pair]['stop'] else "🟢 NOW ") + str(hours_since_first_buy), dispo=dispo, pair=pair, rate=last_candle['close'], @@ -576,18 +540,30 @@ class Zeus_8_3_2_B_4_2(IStrategy): # return 'Drv_' + str(count_of_buys) pair_name = self.getShortName(pair) - if current_profit < - 0.02 and last_candle['sma48'] < before_last_candle['sma48'] - 10: + if current_profit < - 0.02 and last_candle['sma48'] < before_last_candle['sma48'] - 10 and last_candle['sma60_deriv2'] < - 10 and (last_candle['hapercent3'] < -0.0005) and (last_candle['percent'] < 0): + self.pairs[pair]['stop'] = True self.pairs[pair]['force_sell'] = True self.pairs[pair]['force_buy'] = False #(self.pairs[pair]['count_of_buys'] - self.pairs[pair]['has_gain'] > 3) return str(count_of_buys) + '_' + 'stop48_' + pair_name + '_' + str(self.pairs[pair]['has_gain']) - if hours_since_first_buy >= 23.9 : - self.pairs[pair]['force_sell'] = True - self.pairs[pair]['force_buy'] = False #(self.pairs[pair]['count_of_buys'] - self.pairs[pair]['has_gain'] > 3) - return str(count_of_buys) + '_' + 'hours_' + pair_name + '_' + str(self.pairs[pair]['has_gain']) + # if current_profit < - 0.005 and last_candle['sma5_1h'] < before_last_candle_12['sma5_1h'] and hours > 12: + # self.pairs[pair]['force_sell'] = True + # self.pairs[pair]['force_buy'] = False #(self.pairs[pair]['count_of_buys'] - self.pairs[pair]['has_gain'] > 3) + # return str(count_of_buys) + '_' + '5hinv_' + pair_name + '_' + str(self.pairs[pair]['has_gain']) - if last_candle['max_rsi_24'] > 85 and profit > max(5, expected_profit) and (last_candle['hapercent'] < 0) and last_candle['sma60_deriv1'] < 0.05: - self.pairs[pair]['force_sell'] = False + # if last_candle['stop_buying']: + # # self.pairs[pair]['stop'] = True + # self.pairs[pair]['force_sell'] = True + # self.pairs[pair]['force_buy'] = False #(self.pairs[pair]['count_of_buys'] - self.pairs[pair]['has_gain'] > 3) + # return str(count_of_buys) + '_' + 'stopbuy_' + pair_name + '_' + str(self.pairs[pair]['has_gain']) + + # if hours_since_first_buy >= 23.9: + # self.pairs[pair]['force_sell'] = True + # self.pairs[pair]['force_buy'] = True #(self.pairs[pair]['count_of_buys'] - self.pairs[pair]['has_gain'] > 3) + # return str(count_of_buys) + '_' + 'hours_' + pair_name + '_' + str(self.pairs[pair]['has_gain']) + + if last_candle['max_rsi_24'] > 70 and profit > max(5, expected_profit) and (last_candle['hapercent3'] < -0.0005) and (last_candle['percent'] < 0): # and last_candle['sma60_deriv1'] < 0.05: + self.pairs[pair]['force_sell'] = True self.pairs[pair]['force_buy'] = False #(self.pairs[pair]['count_of_buys'] - self.pairs[pair]['has_gain'] > 3) return str(count_of_buys) + '_' + 'Rsi85_' + pair_name + '_' + str(self.pairs[pair]['has_gain']) @@ -596,56 +572,30 @@ class Zeus_8_3_2_B_4_2(IStrategy): self.pairs[pair]['force_buy'] = (self.pairs[pair]['count_of_buys'] - self.pairs[pair]['has_gain'] > 3) return str(count_of_buys) + '_' + 'Frc_' + pair_name + '_' + str(self.pairs[pair]['has_gain']) - 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_name + '_' + str(self.pairs[pair]['has_gain']) - - if max_profit > 0.5 * count_of_buys and baisse > 0.15 and last_candle['sma12_state'] <= 0 and last_candle['sma60_state'] <= - 1: - 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) + '_' + 'B15_' + pair_name + '_' + str(self.pairs[pair]['has_gain']) - - if (last_candle['sma5_1h'] - before_last_candle_12['sma5_1h']) / last_candle['sma5_1h'] > 0.0002: - return None - - factor = 1 - if (self.getShortName(pair) == 'BTC'): - factor = 0.5 - # if baisse > 2 and baisse > factor * self.pairs[pair]['total_amount'] / 100: - # self.pairs[pair]['force_sell'] = False + # 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 'Baisse_' + pair_name + '_' + str(count_of_buys) + '_' + str(self.pairs[pair]['has_gain']) + # return str(count_of_buys) + '_' + 'B30_' + pair_name + '_' + str(self.pairs[pair]['has_gain']) # - # if 1 <= count_of_buys <= 3: - if last_candle['max_rsi_24'] > 75 and profit > expected_profit and (last_candle['hapercent'] < 0) and last_candle['sma60_deriv1'] < 0: - self.pairs[pair]['force_sell'] = False - return str(count_of_buys) + '_' + 'Rsi75_' + pair_name + '_' + str(self.pairs[pair]['has_gain']) + # if (max_profit > 0.5 * count_of_buys or hours > 12) and baisse > 0.15 and last_candle['sma12_state'] <= 0 and last_candle['sma60_state'] <= - 1: + # 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) + '_' + 'B15_' + pair_name + '_' + str(self.pairs[pair]['has_gain']) + if profit > max(5, expected_profit) and last_candle['sma5_inv_bas_1h'] == - 1 and (last_candle['hapercent3'] < -0.0005) and (last_candle['percent'] < 0): + 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) + '_' + 'SMA5_' + pair_name + '_' + str(self.pairs[pair]['has_gain']) - # if last_candle['mid_smooth_1h_deriv1'] < 0 and profit > expected_profit: - # self.pairs[pair]['force_sell'] = False - # self.pairs[pair]['force_buy'] = (self.pairs[pair]['count_of_buys'] - self.pairs[pair]['has_gain'] > 5) - # return str(count_of_buys) + '_' + 'Drv3_' + pair_name + '_' + str(self.pairs[pair]['has_gain']) + if profit < 0 and last_candle['sma5_inv_hau_1h']: + self.pairs[pair]['stop'] = True + 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) + '_' + 'SMA24_' + pair_name + '_' + str(self.pairs[pair]['has_gain']) - # if 4 <= count_of_buys <= 6: - # if ((before_last_candle_2['mid_smooth_12_deriv1'] <= before_last_candle['mid_smooth_12_deriv1']) - # & (before_last_candle['mid_smooth_12_deriv1'] >= last_candle['mid_smooth_12_deriv1'])) \ - # and (current_profit > expected_profit): - # return 'Drv13_' + pair_name + '_' + str(count_of_buys) - # - # if 7 <= count_of_buys: - # if ((before_last_candle_24['sma24_deriv1_1h'] <= before_last_candle_12['sma24_deriv1_1h']) - # & (before_last_candle_12['sma24_deriv1_1h'] >= last_candle['sma24_deriv1_1h'])) \ - # and (current_profit > expected_profit): - # return 'Drv24_' + pair_name + '_' + str(count_of_buys) - - # if (baisse > mx) & (current_profit > expected_profit): - # self.trades = list() - # return 'mx_' + str(count_of_buys) - # if (last_candle['percent12'] <= -0.01) & (current_profit >= expected_profit): - # self.trades = list() - # return 'pft_' + str(count_of_buys) + # if (last_candle['sma48_deriv1'] < -0.1 and last_candle['sma48_deriv2'] < -10): + # self.pairs[pair]['force_sell'] = True + # return str(count_of_buys) + '_' + 'B48D1_' + pair_name + '_' + str(self.pairs[pair]['has_gain']) self.pairs[pair]['max_touch'] = max(last_candle['close'], self.pairs[pair]['max_touch']) @@ -786,12 +736,16 @@ class Zeus_8_3_2_B_4_2(IStrategy): # f"|{round(last_candle['mid_smooth_24_deriv1'],3) or '-':>6}|{round(last_candle['mid_smooth_1h_deriv1'],3) or '-':>6}|{round(last_candle['mid_smooth_deriv1_1d'],3) or '-' :>6}|" # f"{round(last_candle['mid_smooth_24_deriv2'],3) or '-' :>6}|{round(last_candle['mid_smooth_1h_deriv2'],3) or '-':>6}|{round(last_candle['mid_smooth_deriv2_1d'],3) or '-':>6}|" f"{round(last_candle['max_rsi_24'], 1) or '-' :>6}|" - f"{dist_max:>7}|{color_sma24}{round(last_candle['sma24_deriv1_1d'], 2):>5}{RESET}" - f"|{color_sma5}{round(last_candle['mid_smooth_5_deriv1_1d'], 2):>5}{RESET}|{color_sma5_2}{round(last_candle['mid_smooth_5_deriv2_1d'], 2):>5}{RESET}" - f"|{color_sma5_1h}{round(last_candle['sma60_deriv1'], 2):>5}{RESET}|{color_sma5_2h}{round(last_candle['sma60_deriv2'], 2):>5}{RESET}" - f"|{color_smooth_1h}{round(last_candle['mid_smooth_1h_deriv1'], 2):>5}{RESET}|{color_smooth2_1h}{round(last_candle['mid_smooth_1h_deriv2'], 2):>5}{RESET}" - # f"|{last_candle['min60_1d']}|{last_candle['max60_1d']}" - # f"|{last_candle['mid_smooth_tdc_5_1d'] or '-':>3}|{last_candle['mid_smooth_tdc_5_1h'] or '-':>3}|{last_candle['mid_smooth_tdc_5'] or '-':>3}" + f"{dist_max:>7}" + #|s201d + f"|{color_sma24}{round(last_candle['sma24_deriv1_1d'], 2):>5}{RESET}" + #|s5_1d|s5_2d + f"|{color_sma5}{round(last_candle['mid_smooth_5_deriv1_1d'], 2):>5}{RESET}|{color_sma5_2}{round(last_candle['mid_smooth_5_deriv2_1d'], 1):>5}{RESET}" + #|s51h|s52h + f"|{color_sma5_1h}{round(last_candle['sma60_deriv1'], 2):>5}{RESET}|{color_sma5_2h}{round(last_candle['sma60_deriv2'], 1):>5}{RESET}" + #|smt1h|smt2h + f"|{color_smooth_1h}{round(last_candle['mid_smooth_1h_deriv1'], 2):>5}{RESET}|{color_smooth2_1h}{round(last_candle['mid_smooth_1h_deriv2'], 1):>5}{RESET}" + #|tdc1d|tdc1h f"|{last_candle['mid_smooth_5_state_1d'] or '-':>3}|{last_candle['mid_smooth_24_state_1h'] or '-':>3}|{last_candle['mid_smooth_5_state_1h'] or '-':>3}|{last_candle['mid_smooth_5_state'] or '-':>3}" f"|{last_candle['trend_class_1d']:>5} {last_candle['trend_class_1h']:>5}" ) @@ -870,6 +824,9 @@ class Zeus_8_3_2_B_4_2(IStrategy): def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: # Add all ta features pair = metadata['pair'] + short_pair = self.getShortName(pair) + name= type(self).__name__ + self.path = f"user_data/strategies/plots/{name}/{short_pair}/" # + ("valide/" if not self.dp.runmode.value in ('backtest') else '') dataframe = self.populateDataframe(dataframe, timeframe='5m') @@ -950,64 +907,9 @@ class Zeus_8_3_2_B_4_2(IStrategy): # =============================== # Lissage des valeurs Journalières horizon_d = 12 * 5 * 24 - # dataframe['rsi_1h'] = dataframe['rsi_1h'].rolling(12).mean() - # dataframe['rsi_deriv1_1h'] = dataframe['rsi_deriv1_1h'].rolling(12).mean() - # dataframe['rsi_deriv2_1h'] = dataframe['rsi_deriv2_1h'].rolling(12).mean() - # dataframe['mid_smooth_1d'] = dataframe['mid_smooth_1d'].rolling(window=horizon_d * 5).mean() - # dataframe["mid_smooth_deriv1_1d"] = dataframe["mid_smooth_1d"].rolling(horizon_d).mean().diff() / horizon_d - # dataframe["mid_smooth_deriv2_1d"] = horizon_d * dataframe["mid_smooth_deriv1_1d"].rolling(horizon_d).mean().diff() - # - # dataframe['sma5_1d'] = dataframe['sma5_1d'].rolling(window=horizon_d).mean() - # dataframe['sma5_deriv1_1d'] = dataframe['sma5_deriv1_1d'].rolling(window=horizon_d).mean() - # dataframe['sma24_1d'] = dataframe['sma24_1d'].rolling(window=horizon_d).mean() - # dataframe['sma24_deriv1_1d'] = dataframe['sma24_deriv1_1d'].rolling(window=horizon_d).mean() - # dataframe = self.calculateRegression(dataframe, column='mid_smooth_1d', window=24, degree=4, future_offset=12) - - # dataframe['percent_with_previous_day'] = 100 * (dataframe['close'] - dataframe['close_1d']) / dataframe['close'] - # dataframe['percent_with_max_hour'] = 100 * (dataframe['close'] - dataframe['max12_1h']) / dataframe['close'] - # - # horizon_h = 24 * 5 - # dataframe['futur_percent_1h'] = 100 * ((dataframe['mid_smooth_1h'].shift(-12) - dataframe['mid_smooth_1h']) / dataframe['mid_smooth_1h']).rolling(horizon_h).mean() - # dataframe['futur_percent_3h'] = 100 * ((dataframe['close'].shift(-36) - dataframe['close']) / dataframe['close']).rolling(horizon_h).mean() - # dataframe['futur_percent_5h'] = 100 * ((dataframe['mid_smooth_1h'].shift(-60) - dataframe['mid_smooth_1h']) / dataframe['mid_smooth_1h']).rolling(horizon_h).mean() - # dataframe['futur_percent_12h'] = 100 * ((dataframe['mid_smooth_1h'].shift(-144) - dataframe['mid_smooth_1h']) / dataframe['mid_smooth_1h']).rolling(horizon_h).mean() - # - # dataframe['futur_percent_1d'] = 100 * (dataframe['close'].shift(-1) - dataframe['close']) / dataframe['close'] - # dataframe['futur_percent_3d'] = 100 * (dataframe['close'].shift(-3) - dataframe['close']) / dataframe['close'] - # - # self.calculateProbabilite2Index(dataframe, ['futur_percent_1d'], 'sma24_deriv1_1h', 'sma5_1d') - - # if self.dp.runmode.value in ('backtest'): - # print("##################") - # print("# STAT DAY vs HOUR") - # print("##################") - # self.calculateProbabilite2Index(dataframe, futur_cols=['futur_percent_1d'], indic_1='sma5_deriv1_1d', - # indic_2='sma5_deriv2_1d') - - # dataframe['proba_hausse'] = dataframe.apply(lambda row: self.getProbaHausseEmaVolume(row), axis=1) - - # dataframe['futur_percent_3'] = 100 * ((dataframe['sma5'].shift(-1) - dataframe['sma5']) / dataframe['sma5']) - # futur_cols = ['futur_percent_3'] - # indic_1 = 'mid_smooth_1h_deriv1' - # indic_2 = 'mid_smooth_1h_deriv2' - # self.calculateProbabilite2Index(dataframe, futur_cols, indic_1, indic_2) - - # dataframe = dataframe.resample('sma12_1h').ffill() - # dataframe = dataframe.resample('sma24_1h').ffill() - - # mises = IntParameter(1, 50, default=5, space='buy', optimize=False, load=False) - # - # pct = DecimalParameter(0.005, 0.05, default=0.012, decimals=3, space='buy', optimize=True, load=True) - # pct_inc = DecimalParameter(0.0001, 0.003, default=0.0022, decimals=4, space='buy', optimize=True, load=True) - # - # indic_5m_slope_sup = CategoricalParameter(indicators, default="sma60", space='buy') - - indic_5m_protect = self.indic_5m_slope_sup.value - indic_1h_protect = self.indic_1h_slope_sup.value + '_1h' - - dataframe['stop_buying_deb'] = ((dataframe['max_rsi_12_1d'] > self.rsi_buy_protect.value) | (dataframe['sma24_deriv1_1h'] < self.deriv1_buy_protect.value)) & (qtpylib.crossed_below(dataframe[indic_5m_protect], dataframe[indic_1h_protect])) - dataframe['stop_buying_end'] = (dataframe[indic_1h_protect].shift(24) > dataframe[indic_1h_protect].shift(12)) & (dataframe[indic_1h_protect].shift(12) < dataframe[indic_1h_protect]) + dataframe['stop_buying_deb'] = (dataframe['sma48_deriv1'] < - 0.2) & (dataframe['sma5_1h'] < dataframe['sma5_1h'].shift(13)) + dataframe['stop_buying_end'] = (dataframe['sma48_deriv1'] > - 0.2) & (dataframe['sma5_1h'] > dataframe['sma5_1h'].shift(13)) latched = np.zeros(len(dataframe), dtype=bool) @@ -1021,32 +923,117 @@ class Zeus_8_3_2_B_4_2(IStrategy): dataframe['stop_buying'] = latched - if False and self.dp.runmode.value in ('backtest'): - self.trainModel(dataframe, metadata) - - short_pair = self.getShortName(pair) - # if short_pair == 'BTC': - # self.model = joblib.load(f"{short_pair}_rf_model.pkl") + self.model_indicators = self.listUsableColumns(dataframe) + # if False and self.dp.runmode.value in ('backtest'): + # self.trainModel(dataframe, metadata) # - # # Préparer les features pour la prédiction - # features = dataframe[self.model_indicators].fillna(0) + # short_pair = self.getShortName(pair) # - # # Prédiction : probabilité que le prix monte - # probs = self.model.predict_proba(features)[:, 1] + # self.model = joblib.load(f"{self.path}/{short_pair}_rf_model.pkl") # - # # Sauvegarder la probabilité pour l’analyse - # dataframe['ml_prob'] = probs + # # Préparer les features pour la prédiction + # features = dataframe[self.model_indicators].fillna(0) # + # # Prédiction : probabilité que le prix monte + # + # # Affichage des colonnes intérressantes dans le model + # features_pruned, kept_features = self.prune_features( + # model=self.model, + # dataframe=dataframe, + # feature_columns=self.model_indicators, + # importance_threshold=0.005 # enlever features < % importance + # ) + # + # probs = self.model.predict_proba(features)[:, 1] + # + # # Sauvegarder la probabilité pour l’analyse + # dataframe['ml_prob'] = probs + # + # if False and self.dp.runmode.value in ('backtest'): # self.inspect_model(self.model) return dataframe + def prune_features(self, model, dataframe, feature_columns, importance_threshold=0.01): + """ + Supprime les features dont l'importance est inférieure au seuil. + + Args: + model: XGBClassifier déjà entraîné + dataframe: DataFrame contenant toutes les features + feature_columns: liste des colonnes/features utilisées pour la prédiction + importance_threshold: seuil minimal pour conserver une feature (en proportion de l'importance totale) + + Returns: + dataframe_pruned: dataframe avec uniquement les features conservées + kept_features: liste des features conservées + """ + booster = model.get_booster() + + # Récupérer importance des features selon 'gain' + importance = booster.get_score(importance_type='gain') + + # Normaliser pour que la somme soit 1 + total_gain = sum(importance.values()) + normalized_importance = {k: v / total_gain for k, v in importance.items()} + + # Features à garder + kept_features = [f for f in feature_columns if normalized_importance.get(f, 0) >= importance_threshold] + + dataframe_pruned = dataframe[kept_features].fillna(0) + + # print(f"⚡ Features conservées ({len(kept_features)} / {len(feature_columns)}): {kept_features}") + + return dataframe_pruned, kept_features + + + def listUsableColumns(self, dataframe): + # Étape 1 : sélectionner numériques + numeric_cols = dataframe.select_dtypes(include=['int64', 'float64']).columns + # Étape 2 : enlever constantes + usable_cols = [c for c in numeric_cols if dataframe[c].nunique() > 1 + and ( + ("deriv" in c or "dist" in c) + and ("_1h" in c or "_1d" in c) + ) + # and not "smooth" in c + and not c.endswith("_state") + # and not c.endswith("_1d") + # and not c.endswith("_1h") + and not c.startswith("open") and not c.startswith("close") + and not c.startswith("low") and not c.startswith("high") + and not c.startswith("haopen") and not c.startswith("haclose") + # and not c.startswith("bb_lower") and not c.startswith("bb_upper") + # and not c.startswith("bb_middle") + and not c.endswith("_count") + and not c.endswith("_class") and not c.endswith("_price") + and not c.startswith('stop_buying') + and not c.startswith('target') + and not c.startswith('lvl') + # and not c.startswith('sma5_deriv1_1h') + # and not c.startswith('sma5_1h') + # and not c.startswith('sma12_deriv1_1h') + # and not c.startswith('sma12_1h') + # and not c.startswith('confidence_index') + # and not c.startswith('price_change') + # and not c.startswith('price_score') + # and not c.startswith('heat_score') + # and not c.startswith('min30_1d') + # and not c.startswith('max30_1d') + ] + # Étape 3 : remplacer inf et NaN par 0 + dataframe[usable_cols] = dataframe[usable_cols].replace([np.inf, -np.inf], 0).fillna(0) + print("Colonnes utilisables pour le modèle :") + print(usable_cols) + self.model_indicators = usable_cols + return usable_cols + def trainModel(self, dataframe: DataFrame, metadata: dict): pair = self.getShortName(metadata['pair']) pd.set_option('display.max_rows', None) pd.set_option('display.max_columns', None) pd.set_option("display.width", 200) - path=f"user_data/plots/{pair}/" + path = self.path # f"user_data/plots/{pair}/" os.makedirs(path, exist_ok=True) # # Étape 1 : sélectionner numériques @@ -1075,8 +1062,8 @@ class Zeus_8_3_2_B_4_2(IStrategy): # 3️⃣ Créer la cible : 1 si le prix monte dans les prochaines bougies # df['target'] = (df['sma24'].shift(-24) > df['sma24']).astype(int) - df['target'] = (df['percent12'].shift(-13) > 0.0015).astype(int) - df['target'] = df['target'].fillna(0).astype(int) + dataframe['target'] = (dataframe['sma5_1h'].shift(-48) > dataframe['sma5_1h']).astype(int) + df['target'] = dataframe['target'].fillna(0).astype(int) # Corrélations triées par importance avec une colonne cible target_corr = df.corr(numeric_only=True)["target"].sort_values(ascending=False) @@ -1127,7 +1114,7 @@ class Zeus_8_3_2_B_4_2(IStrategy): plt.yticks(rotation=0) # --- Sauvegarde --- - output_path = f"{path}/Matrice_de_correlation_temperature.png" + output_path = f"{self.path}/Matrice_de_correlation_temperature.png" plt.savefig(output_path, bbox_inches="tight", dpi=150) plt.close(fig) @@ -1150,7 +1137,7 @@ class Zeus_8_3_2_B_4_2(IStrategy): X = df[self.model_indicators] y = df['target'] # Séparation temporelle (train = 80 %, valid = 20 %) - X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, shuffle=False) + X_train, X_valid, y_train, y_valid = train_test_split(X, y, test_size=0.2, shuffle=False) # Nettoyage des valeurs invalides @@ -1160,72 +1147,234 @@ class Zeus_8_3_2_B_4_2(IStrategy): print("Colonnes conservées :", list(selected)) # 5️⃣ Entraînement du modèle - # train_model = RandomForestClassifier(n_estimators=200, random_state=42) - train_model = RandomForestClassifier( - n_estimators=300, - max_depth=12, - # min_samples_split=4, - # min_samples_leaf=2, - # max_features='sqrt', - # random_state=42, - # n_jobs=-1, - class_weight='balanced' - ) - # 1️⃣ Entraîne ton modèle LGBM normal - # train_model = LGBMClassifier( - # n_estimators=800, - # learning_rate=0.02, - # max_depth=10, - # num_leaves=31, - # subsample=0.8, - # colsample_bytree=0.8, - # reg_alpha=0.2, - # reg_lambda=0.4, - # class_weight='balanced', - # random_state=42, - # ) + # self.train_model = RandomForestClassifier(n_estimators=200, random_state=42) - train_model.fit(X_train, y_train) + # def objective(trial): + # self.train_model = XGBClassifier( + # n_estimators=trial.suggest_int("n_estimators", 200, 300), + # max_depth=trial.suggest_int("max_depth", 3, 6), + # learning_rate=trial.suggest_float("learning_rate", 0.01, 0.3), + # subsample=trial.suggest_float("subsample", 0.7, 1.0), + # colsample_bytree=trial.suggest_float("colsample_bytree", 0.7, 1.0), + # scale_pos_weight=1, # tu mettras balance_ratio ici si tu veux + # objective="binary:logistic", + # eval_metric="logloss", + # n_jobs=-1 + # ) + # + # self.train_model.fit(X_train, y_train) + # + # y_pred = self.train_model.predict(X_valid) # <-- validation = test split + # return f1_score(y_valid, y_pred) + # + # study = optuna.create_study(direction="maximize") + # study.optimize(objective, n_trials=50) + + def objective(trial): + # local_model = XGBClassifier( + # n_estimators=300, # nombre d'arbres plus raisonnable + # learning_rate=0.01, # un peu plus rapide que 0.006, mais stable + # max_depth=4, # capture plus de patterns que 3, sans overfitting excessif + # subsample=0.7, # utilise 70% des lignes pour chaque arbre → réduit overfitting + # colsample_bytree=0.8, # 80% des features par arbre + # gamma=0.01, # gain minimal pour un split → régularisation + # reg_alpha=0.01, # L1 régularisation des feuilles + # reg_lambda=1, # L2 régularisation des feuilles + # n_jobs=-1, # utilise tous les cœurs CPU pour accélérer + # random_state=42, # reproductibilité + # missing=float('nan'), # valeur manquante reconnue + # eval_metric='logloss' # métrique pour classification binaire + # ) + + local_model = XGBClassifier( + n_estimators=trial.suggest_int("n_estimators", 300, 500), + max_depth=trial.suggest_int("max_depth", 1, 6), + learning_rate=trial.suggest_float("learning_rate", 0.005, 0.3, log=True), + subsample=trial.suggest_float("subsample", 0.6, 1.0), + colsample_bytree=trial.suggest_float("colsample_bytree", 0.6, 1.0), + scale_pos_weight=1, + objective="binary:logistic", + eval_metric="logloss", + n_jobs=-1 + ) + + local_model.fit( + X_train, + y_train, + eval_set=[(X_valid, y_valid)], + # early_stopping_rounds=50, + verbose=False + ) + + proba = local_model.predict_proba(X_valid)[:, 1] + thresholds = np.linspace(0.1, 0.9, 50) + best_f1 = max(f1_score(y_valid, (proba > t)) for t in thresholds) + + return best_f1 + + study = optuna.create_study(direction="maximize") + study.optimize(objective, n_trials=20) + + # SHAP + # Reconstruction du modèle final avec les meilleurs hyperparamètres + # Récupération des meilleurs paramètres trouvés + best_params = study.best_params + + best_model = XGBClassifier(**best_params) + best_model.fit(X_train, y_train) + self.train_model = best_model + + # === SHAP plots === + # Calcul SHAP + explainer = shap.TreeExplainer(self.train_model) + shap_values = explainer(X_train) + + # On choisit une observation pour le graphique waterfall + # Explication du modèle de prédiction pour la première ligne de X_valid.” + i = 0 + + # Extraction des valeurs + shap_val = shap_values[i].values + feature_names = X_train.columns + feature_values = X_train.iloc[i] + + # Tri par importance absolue + # order = np.argsort(np.abs(shap_val))[::-1] + k = 10 + order = np.argsort(np.abs(shap_val))[::-1][:k] + + # ---- Création figure sans l'afficher ---- + plt.ioff() # Désactive l'affichage interactif + + shap.plots.waterfall( + shap.Explanation( + values=shap_val[order], + base_values=shap_values.base_values[i], + data=feature_values.values[order], + feature_names=feature_names[order] + ), + show=False # IMPORTANT : n'affiche pas dans Jupyter / console + ) + + # Sauvegarde du graphique sur disque + output_path = f"{self.path}/shap_waterfall.png" + plt.savefig(output_path, dpi=200, bbox_inches='tight') + plt.close() # ferme la figure proprement + + print(f"Graphique SHAP enregistré : {output_path}") + + # FIN SHAP + # ---- après avoir exécuté la study ------ + + print("Best value (F1):", study.best_value) + print("Best params:", study.best_params) + + best_trial = study.best_trial + print("\n=== BEST TRIAL ===") + print("Number:", best_trial.number) + print("Value:", best_trial.value) + print("Params:") + for k, v in best_trial.params.items(): + print(f" - {k}: {v}") + + # All trials summary + print("\n=== ALL TRIALS ===") + for t in study.trials: + print(f"Trial {t.number}: f1 = {t.value}, params = {t.params}") + + # DataFrame of trials + df = study.trials_dataframe() + print(df.head()) + + # Graphs + fig = plot_optimization_history(study) + fig.write_html(f"{self.path}/optimization_history.html") + fig = plot_param_importances(study) + fig.write_html(f"{self.path}/param_importances.html") + fig = plot_slice(study) + fig.write_html(f"{self.path}/slice.html") + fig = plot_parallel_coordinate(study) + fig.write_html(f"{self.path}/parallel_coordinates.html") # 2️⃣ Sélection des features AVANT calibration - sfm = SelectFromModel(train_model, threshold="median", prefit=True) + sfm = SelectFromModel(self.train_model, threshold="median", prefit=True) selected_features = X_train.columns[sfm.get_support()] print(selected_features) # 3️⃣ Calibration ensuite (facultative) - calibrated = CalibratedClassifierCV(train_model, method='sigmoid', cv=5) + calibrated = CalibratedClassifierCV(self.train_model, method='sigmoid', cv=5) calibrated.fit(X_train[selected_features], y_train) print(calibrated) # # # calibration - # train_model = CalibratedClassifierCV(train_model, method='sigmoid', cv=5) + # self.train_model = CalibratedClassifierCV(self.train_model, method='sigmoid', cv=5) # # Sélection - # sfm = SelectFromModel(train_model, threshold="median") + # sfm = SelectFromModel(self.train_model, threshold="median") # sfm.fit(X_train, y_train) # selected_features = X_train.columns[sfm.get_support()] # print(selected_features) - train_model.fit(X_train, y_train) - y_pred = train_model.predict(X_test) - y_proba = train_model.predict_proba(X_test)[:, 1] - # print(classification_report(y_test, y_pred)) - # print(confusion_matrix(y_test, y_pred)) - print("\nRapport de classification :\n", classification_report(y_test, y_pred)) - print("\nMatrice de confusion :\n", confusion_matrix(y_test, y_pred)) + # self.train_model.fit(X_train, y_train) + + y_pred = self.train_model.predict(X_valid) + y_proba = self.train_model.predict_proba(X_valid)[:, 1] + # print(classification_report(y_valid, y_pred)) + # print(confusion_matrix(y_valid, y_pred)) + print("\nRapport de classification :\n", classification_report(y_valid, y_pred)) + print("\nMatrice de confusion :\n", confusion_matrix(y_valid, y_pred)) # # Importances # importances = pd.DataFrame({ - # "feature": train_model.feature_name_, - # "importance": train_model.feature_importances_ + # "feature": self.train_model.feature_name_, + # "importance": self.train_model.feature_importances_ # }).sort_values("importance", ascending=False) # print("\n===== 🔍 IMPORTANCE DES FEATURES =====") # print(importances) + # Feature importance + importances = self.train_model.feature_importances_ + feat_imp = pd.Series(importances, index=X_train.columns).sort_values(ascending=False) + + # Affichage + feat_imp.plot(kind='bar', figsize=(12, 6)) + plt.title("Feature importances") + # plt.show() + plt.savefig(f"{self.path}/Feature importances.png", bbox_inches='tight') + + result = permutation_importance(self.train_model, X_valid, y_valid, scoring='f1', n_repeats=10, random_state=42) + perm_imp = pd.Series(result.importances_mean, index=X_valid.columns).sort_values(ascending=False) + perm_imp.plot(kind='bar', figsize=(12, 6)) + plt.title("Permutation feature importance") + # plt.show() + plt.savefig(f"{self.path}/Permutation feature importance.png", bbox_inches='tight') + + # Shap + explainer = shap.TreeExplainer(self.train_model) + shap_values = explainer.shap_values(X_valid) + + # Résumé global + shap.summary_plot(shap_values, X_valid) + + # Force plot pour une observation + force_plot = shap.force_plot(explainer.expected_value, shap_values[0, :], X_valid.iloc[0, :]) + shap.save_html(f"{self.path}/shap_force_plot.html", force_plot) + + fig, ax = plt.subplots(figsize=(24, 48)) + PartialDependenceDisplay.from_estimator( + self.train_model, + X_valid, + selected_features, + kind="average", + ax=ax + ) + fig.savefig(f"{self.path}/PartialDependenceDisplay.png", bbox_inches="tight") + plt.close(fig) + best_f1 = 0 best_t = 0.5 for t in [0.3, 0.4, 0.5, 0.6, 0.7]: y_pred_thresh = (y_proba > t).astype(int) - score = f1_score(y_test, y_pred_thresh) + score = f1_score(y_valid, y_pred_thresh) print(f"Seuil {t:.1f} → F1: {score:.3f}") if score > best_f1: best_f1 = score @@ -1234,12 +1383,12 @@ class Zeus_8_3_2_B_4_2(IStrategy): print(f"✅ Meilleur seuil trouvé: {best_t} avec F1={best_f1:.3f}") # 6️⃣ Évaluer la précision (facultatif) - preds = train_model.predict(X_test) - acc = accuracy_score(y_test, preds) + preds = self.train_model.predict(X_valid) + acc = accuracy_score(y_valid, preds) print(f"Accuracy: {acc:.3f}") # 7️⃣ Sauvegarde du modèle - joblib.dump(train_model, f"{pair}_rf_model.pkl") + joblib.dump(self.train_model, f"{self.path}/{pair}_rf_model.pkl") print(f"✅ Modèle sauvegardé sous {pair}_rf_model.pkl") # X = dataframe des features (après shift/rolling/indicators) @@ -1251,7 +1400,7 @@ class Zeus_8_3_2_B_4_2(IStrategy): # mi_series = pd.Series(mi_scores, index=X.columns, name='MI') # # # --- 2️⃣ Permutation Importance (PI) --- - # pi_result = permutation_importance(train_model, X, y, n_repeats=10, random_state=42, n_jobs=-1) + # pi_result = permutation_importance(self.train_model, X, y, n_repeats=10, random_state=42, n_jobs=-1) # pi_series = pd.Series(pi_result.importances_mean, index=X.columns, name='PI') # # # --- 3️⃣ Combinaison dans un seul dataframe --- @@ -1264,7 +1413,7 @@ class Zeus_8_3_2_B_4_2(IStrategy): # plt.ylabel("Score") # plt.show() - self.analyze_model(pair, train_model, X_train, X_test, y_train, y_test) + self.analyze_model(pair, self.train_model, X_train, X_valid, y_train, y_valid) def inspect_model(self, model): """ @@ -1338,28 +1487,27 @@ class Zeus_8_3_2_B_4_2(IStrategy): print("\n===== ✅ FIN DE L’INSPECTION =====") - def analyze_model(self, pair, model, X_train, X_test, y_train, y_test): + def analyze_model(self, pair, model, X_train, X_valid, y_train, y_valid): """ Analyse complète d'un modèle ML supervisé (classification binaire). Affiche performances, importance des features, matrices, seuils, etc. """ - output_dir = f"user_data/plots/{pair}/" - os.makedirs(output_dir, exist_ok=True) + os.makedirs(self.path, exist_ok=True) # ---- Prédictions ---- - preds = model.predict(X_test) - probs = model.predict_proba(X_test)[:, 1] if hasattr(model, "predict_proba") else preds + preds = model.predict(X_valid) + probs = model.predict_proba(X_valid)[:, 1] if hasattr(model, "predict_proba") else preds # ---- Performances globales ---- print("===== 📊 ÉVALUATION DU MODÈLE =====") print("Colonnes du modèle :", model.feature_names_in_) - print("Colonnes X_test :", list(X_test.columns)) - print(f"Accuracy: {accuracy_score(y_test, preds):.3f}") - print(f"ROC AUC : {roc_auc_score(y_test, probs):.3f}") + print("Colonnes X_valid :", list(X_valid.columns)) + print(f"Accuracy: {accuracy_score(y_valid, preds):.3f}") + print(f"ROC AUC : {roc_auc_score(y_valid, probs):.3f}") print("TN (True Negative) / FP (False Positive)") print("FN (False Negative) / TP (True Positive)") - print("\nRapport de classification :\n", classification_report(y_test, preds)) + print("\nRapport de classification :\n", classification_report(y_valid, preds)) # | Élément | Valeur | Signification | # | ------------------- | ------ | ----------------------------------------------------------- | @@ -1369,7 +1517,7 @@ class Zeus_8_3_2_B_4_2(IStrategy): # | TP (True Positive) | 19 | Modèle a correctement prédit 1 (bon signal d’achat) | # ---- Matrice de confusion ---- - cm = confusion_matrix(y_test, preds) + cm = confusion_matrix(y_valid, preds) print("Matrice de confusion :\n", cm) plt.figure(figsize=(4, 4)) @@ -1381,7 +1529,7 @@ class Zeus_8_3_2_B_4_2(IStrategy): for j in range(2): plt.text(j, i, cm[i, j], ha="center", va="center", color="black") # plt.show() - plt.savefig(os.path.join(output_dir, "Matrice de confusion.png"), bbox_inches="tight") + plt.savefig(os.path.join(self.path, "Matrice de confusion.png"), bbox_inches="tight") plt.close() # ---- Importance des features ---- @@ -1404,7 +1552,7 @@ class Zeus_8_3_2_B_4_2(IStrategy): plt.title("Importance des features") # plt.show() - plt.savefig(os.path.join(output_dir, "Importance des features.png"), bbox_inches="tight") + plt.savefig(os.path.join(self.path, "Importance des features.png"), bbox_inches="tight") plt.close() # ---- Arbre de décision (extrait) ---- @@ -1417,11 +1565,11 @@ class Zeus_8_3_2_B_4_2(IStrategy): print("\n===== ⚙️ PERFORMANCE SELON SEUIL =====") for t in thresholds: preds_t = (probs > t).astype(int) - acc = accuracy_score(y_test, preds_t) + acc = accuracy_score(y_valid, preds_t) print(f"Seuil {t:.1f} → précision {acc:.3f}") # ---- ROC Curve ---- - fpr, tpr, _ = roc_curve(y_test, probs) + fpr, tpr, _ = roc_curve(y_valid, probs) plt.figure(figsize=(5, 4)) plt.plot(fpr, tpr, label="ROC curve") plt.plot([0, 1], [0, 1], linestyle="--", color="gray") @@ -1430,7 +1578,7 @@ class Zeus_8_3_2_B_4_2(IStrategy): plt.title("Courbe ROC") plt.legend() # plt.show() - plt.savefig(os.path.join(output_dir, "Courbe ROC.png"), bbox_inches="tight") + plt.savefig(os.path.join(self.path, "Courbe ROC.png"), bbox_inches="tight") plt.close() # # ---- Interprétation SHAP (optionnelle) ---- @@ -1439,8 +1587,8 @@ class Zeus_8_3_2_B_4_2(IStrategy): # # print("\n===== 💡 ANALYSE SHAP =====") # explainer = shap.TreeExplainer(model) - # shap_values = explainer.shap_values(X_test) - # # shap.summary_plot(shap_values[1], X_test) + # shap_values = explainer.shap_values(X_valid) + # # shap.summary_plot(shap_values[1], X_valid) # # Vérifie le type de sortie de shap_values # if isinstance(shap_values, list): # # Cas des modèles de classification (plusieurs classes) @@ -1449,39 +1597,39 @@ class Zeus_8_3_2_B_4_2(IStrategy): # shap_values_to_plot = shap_values # # # Ajustement des dimensions au besoin - # if shap_values_to_plot.shape[1] != X_test.shape[1]: - # print(f"⚠️ Mismatch dimensions SHAP ({shap_values_to_plot.shape[1]}) vs X_test ({X_test.shape[1]})") - # min_dim = min(shap_values_to_plot.shape[1], X_test.shape[1]) + # if shap_values_to_plot.shape[1] != X_valid.shape[1]: + # print(f"⚠️ Mismatch dimensions SHAP ({shap_values_to_plot.shape[1]}) vs X_valid ({X_valid.shape[1]})") + # min_dim = min(shap_values_to_plot.shape[1], X_valid.shape[1]) # shap_values_to_plot = shap_values_to_plot[:, :min_dim] - # X_to_plot = X_test.iloc[:, :min_dim] + # X_to_plot = X_valid.iloc[:, :min_dim] # else: - # X_to_plot = X_test + # X_to_plot = X_valid # # plt.figure(figsize=(12, 4)) # shap.summary_plot(shap_values_to_plot, X_to_plot, show=False) - # plt.savefig(os.path.join(output_dir, "shap_summary.png"), bbox_inches="tight") + # plt.savefig(os.path.join(self.path, "shap_summary.png"), bbox_inches="tight") # plt.close() # except ImportError: # print("\n(SHAP non installé — `pip install shap` pour activer l’analyse SHAP.)") - y_proba = model.predict_proba(X_test)[:, 1] + y_proba = model.predict_proba(X_valid)[:, 1] # Trace ou enregistre le graphique - self.plot_threshold_analysis(y_test, y_proba, step=0.05, - save_path=f"{output_dir}/threshold_analysis.png") + self.plot_threshold_analysis(y_valid, y_proba, step=0.05, + save_path=f"{self.path}/threshold_analysis.png") - # y_test : vraies classes (0 / 1) + # y_valid : vraies classes (0 / 1) # y_proba : probabilités de la classe 1 prédites par ton modèle - # Exemple : y_proba = model.predict_proba(X_test)[:, 1] + # Exemple : y_proba = model.predict_proba(X_valid)[:, 1] seuils = np.arange(0.0, 1.01, 0.05) precisions, recalls, f1s = [], [], [] for seuil in seuils: y_pred = (y_proba >= seuil).astype(int) - precisions.append(precision_score(y_test, y_pred)) - recalls.append(recall_score(y_test, y_pred)) - f1s.append(f1_score(y_test, y_pred)) + precisions.append(precision_score(y_valid, y_pred)) + recalls.append(recall_score(y_valid, y_pred)) + f1s.append(f1_score(y_valid, y_pred)) plt.figure(figsize=(10, 6)) plt.plot(seuils, precisions, label='Précision', marker='o') @@ -1497,7 +1645,7 @@ class Zeus_8_3_2_B_4_2(IStrategy): plt.ylabel("Score") plt.grid(True, alpha=0.3) plt.legend() - plt.savefig(f"{output_dir}/seuil_de_probabilite.png", bbox_inches='tight') + plt.savefig(f"{self.path}/seuil_de_probabilite.png", bbox_inches='tight') # plt.show() print(f"✅ Meilleur F1 : {f1s[best_idx]:.3f} au seuil {seuils[best_idx]:.2f}") @@ -1514,11 +1662,8 @@ class Zeus_8_3_2_B_4_2(IStrategy): """ # Le graphique généré affichera trois courbes : - # # 🔵 Precision — la fiabilité de tes signaux haussiers. - # # 🟢 Recall — la proportion de hausses que ton modèle détecte. - # # 🟣 F1-score — le compromis optimal entre les deux. thresholds = np.arange(0, 1.01, step) @@ -1574,9 +1719,10 @@ class Zeus_8_3_2_B_4_2(IStrategy): dataframe['mid'] = dataframe['haopen'] + (dataframe['haclose'] - dataframe['haopen']) / 2 dataframe["percent"] = dataframe['close'].pct_change() - dataframe["percent3"] = dataframe['close'].pct_change(3) + dataframe["percent5"] = dataframe['close'].pct_change(5) dataframe["percent12"] = dataframe['close'].pct_change(12) dataframe["percent24"] = dataframe['close'].pct_change(24) + dataframe["hapercent3"] = (dataframe['haclose'] - dataframe['haopen'].shift(3)) / dataframe['haclose'].shift(3) # if self.dp.runmode.value in ('backtest'): # dataframe['futur_percent'] = 100 * (dataframe['close'].shift(-1) - dataframe['close']) / dataframe['close'] @@ -1601,6 +1747,7 @@ class Zeus_8_3_2_B_4_2(IStrategy): dataframe['max_rsi_12'] = talib.MAX(dataframe['rsi'], timeperiod=12) dataframe['max_rsi_24'] = talib.MAX(dataframe['rsi'], timeperiod=24) self.calculeDerivees(dataframe, 'rsi', timeframe=timeframe, ema_period=12) + dataframe['min12'] = talib.MIN(dataframe['close'], timeperiod=12) dataframe['max12'] = talib.MAX(dataframe['close'], timeperiod=12) dataframe['max60'] = talib.MAX(dataframe['close'], timeperiod=60) dataframe['min60'] = talib.MIN(dataframe['close'], timeperiod=60) @@ -1704,27 +1851,27 @@ class Zeus_8_3_2_B_4_2(IStrategy): ).average_true_range() dataframe['atr_norm'] = dataframe['atr'] / dataframe['close'] - # --- Force de tendance --- - dataframe['adx'] = ta.trend.ADXIndicator( - high=dataframe['high'], low=dataframe['low'], close=dataframe['close'], window=14 - ).adx() - - # --- Volume directionnel (On Balance Volume) --- - dataframe['obv'] = ta.volume.OnBalanceVolumeIndicator( - close=dataframe['close'], volume=dataframe['volume'] - ).on_balance_volume() - self.calculeDerivees(dataframe, 'obv', timeframe=timeframe, ema_period=1) - - dataframe['obv5'] = ta.volume.OnBalanceVolumeIndicator( - close=dataframe['sma5'], volume=dataframe['volume'].rolling(5).sum() - ).on_balance_volume() - self.calculeDerivees(dataframe, 'obv5', timeframe=timeframe, ema_period=5) + # # --- Force de tendance --- + # dataframe['adx'] = ta.trend.ADXIndicator( + # high=dataframe['high'], low=dataframe['low'], close=dataframe['close'], window=14 + # ).adx() + # + # # --- Volume directionnel (On Balance Volume) --- + # dataframe['obv'] = ta.volume.OnBalanceVolumeIndicator( + # close=dataframe['close'], volume=dataframe['volume'] + # ).on_balance_volume() + # self.calculeDerivees(dataframe, 'obv', timeframe=timeframe, ema_period=1) + # + # dataframe['obv5'] = ta.volume.OnBalanceVolumeIndicator( + # close=dataframe['sma5'], volume=dataframe['volume'].rolling(5).sum() + # ).on_balance_volume() + # self.calculeDerivees(dataframe, 'obv5', timeframe=timeframe, ema_period=5) # --- Volatilité récente (écart-type des rendements) --- - dataframe['vol_24'] = dataframe['percent'].rolling(24).std() + # dataframe['vol_24'] = dataframe['percent'].rolling(24).std() # Compter les baisses / hausses consécutives - self.calculateDownAndUp(dataframe, limit=0.0001) + # self.calculateDownAndUp(dataframe, limit=0.0001) # df : ton dataframe OHLCV + indicateurs existants # Assurez-vous que les colonnes suivantes existent : @@ -1733,18 +1880,18 @@ class Zeus_8_3_2_B_4_2(IStrategy): # --- Filtrage des NaN initiaux --- # dataframe = dataframe.dropna() - dataframe['rsi_slope'] = dataframe['rsi'].diff(3) / 3 # vitesse moyenne du RSI - dataframe['adx_change'] = dataframe['adx'] - dataframe['adx'].shift(12) # évolution de la tendance - dataframe['volatility_ratio'] = dataframe['atr_norm'] / dataframe['bb_width'] + # dataframe['rsi_slope'] = dataframe['rsi'].diff(3) / 3 # vitesse moyenne du RSI + # dataframe['adx_change'] = dataframe['adx'] - dataframe['adx'].shift(12) # évolution de la tendance + # dataframe['volatility_ratio'] = dataframe['atr_norm'] / dataframe['bb_width'] - dataframe["rsi_diff"] = dataframe["rsi"] - dataframe["rsi"].shift(3) - dataframe["slope_ratio"] = dataframe["sma5_deriv1"] / (dataframe["sma60_deriv1"] + 1e-9) - dataframe["divergence"] = (dataframe["rsi_deriv1"] * dataframe["sma5_deriv1"]) < 0 + # dataframe["rsi_diff"] = dataframe["rsi"] - dataframe["rsi"].shift(3) + # dataframe["slope_ratio"] = dataframe["sma5_deriv1"] / (dataframe["sma60_deriv1"] + 1e-9) + # dataframe["divergence"] = (dataframe["rsi_deriv1"] * dataframe["sma5_deriv1"]) < 0 ########################### - - dataframe['volume_sma_deriv'] = dataframe['volume'] * dataframe['sma5_deriv1'] / (dataframe['volume'].rolling(5).mean()) - self.calculeDerivees(dataframe, 'volume', timeframe=timeframe, ema_period=12) + # + # dataframe['volume_sma_deriv'] = dataframe['volume'] * dataframe['sma5_deriv1'] / (dataframe['volume'].rolling(5).mean()) + # self.calculeDerivees(dataframe, 'volume', timeframe=timeframe, ema_period=12) self.setTrends(dataframe) @@ -1841,6 +1988,7 @@ class Zeus_8_3_2_B_4_2(IStrategy): Calcule deriv1/deriv2 (relative simple), applique EMA, calcule tendency avec epsilon adaptatif basé sur rolling percentiles. """ + # dataframe = dataframe.copy() d1_col = f"{name}{suffixe}_deriv1" d2_col = f"{name}{suffixe}_deriv2" @@ -1851,12 +1999,17 @@ class Zeus_8_3_2_B_4_2(IStrategy): factor1 = 100 * (ema_period / 5) factor2 = 10 * (ema_period / 5) - dataframe[f"{name}{suffixe}_inv"] = (dataframe[f"{name}{suffixe}"].shift(2) >= dataframe[f"{name}{suffixe}"].shift(1)) \ - & (dataframe[f"{name}{suffixe}"].shift(1) <= dataframe[f"{name}{suffixe}"]) + series = dataframe[f"{name}{suffixe}"] + + cond_bas = (series.shift(2) > series.shift(1)) & (series.shift(1) < series) + cond_haut = (series.shift(2) < series.shift(1)) & (series.shift(1) > series) + + dataframe[f"{name}{suffixe}_inv_bas"] = np.where(cond_bas, -1, 0) + dataframe[f"{name}{suffixe}_inv_hau"] = np.where(cond_haut, 1, 0) + # --- Distance à la moyenne mobile --- dataframe[f"{name}{suffixe}_dist"] = (dataframe['close'] - dataframe[f"{name}{suffixe}"]) / dataframe[f"{name}{suffixe}"] - # dérivée relative simple dataframe[d1_col] = factor1 * ((dataframe[name] - dataframe[name].shift(3)) / dataframe[name].shift(3)) # lissage EMA @@ -1864,7 +2017,7 @@ class Zeus_8_3_2_B_4_2(IStrategy): # dataframe[d1_col] = dataframe[d1_col].rolling(window=ema_period, center=True).median() - dataframe[d2_col] = factor2 * (dataframe[d1_col] - dataframe[d1_col].shift(1)) + dataframe[d2_col] = (dataframe[d1_col] - dataframe[d1_col].shift(1)) # dataframe[d2_col] = factor2 * dataframe[d2_col].ewm(span=ema_period, adjust=False).mean() # epsilon adaptatif via rolling percentile @@ -1944,14 +2097,70 @@ class Zeus_8_3_2_B_4_2(IStrategy): def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: pair = metadata['pair'] + # Backtested 2025-04-09 00:00:00 -> 2025-05-25 00:00:00 | Max open trades : 1 + # ┏━━━━━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┓ + # ┃ Strategy ┃ Trades ┃ Avg Profit % ┃ Tot Profit USDC ┃ Tot Profit % ┃ Avg Duration ┃ Win Draw Loss Win% ┃ Drawdown ┃ + # ┡━━━━━━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━┩ + # │ Zeus_8_3_2_B_4_2 │ 76 │ 0.17 │ 126.862 │ 12.69 │ 12:51:00 │ 51 0 25 67.1 │ 55.742 USDC 4.71% │ + # └──────────────────┴────────┴──────────────┴─────────────────┴──────────────┴──────────────┴────────────────────────┴────────────────────┘ + # dataframe.loc[ + # ( + # # (dataframe["mid_smooth_5_deriv1_1d"] > 0) + # (dataframe["percent12"] > 0) + # & (dataframe['sma48'] > dataframe['sma48'].shift(1)) + # # & (dataframe['sma48'] < dataframe['sma48'].shift(1) + 10) + # & (dataframe['sma5_1h'] >= dataframe['sma5_1h'].shift(13)) + # & (dataframe['stop_buying'] == False) + # # & (dataframe['trend_class_1h'] <= -1) + # # & (dataframe['mid_smooth_5_state_1d'] >= -2) #or '-':>3}|{last_candle['mid_smooth_24_state_1h'] ) + # ), ['enter_long', 'enter_tag']] = (1, 'sma48') + + + # Backtested 2025-04-09 00:00:00 -> 2025-05-25 00:00:00 | Max open trades : 1 + # ┏━━━━━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┓ + # ┃ Strategy ┃ Trades ┃ Avg Profit % ┃ Tot Profit USDC ┃ Tot Profit % ┃ Avg Duration ┃ Win Draw Loss Win% ┃ Drawdown ┃ + # ┡━━━━━━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━┩ + # │ Zeus_8_3_2_B_4_2 │ 60 │ 0.17 │ 103.408 │ 10.34 │ 12:52:00 │ 44 0 16 73.3 │ 38.701 USDC 3.53% │ + # └──────────────────┴────────┴──────────────┴─────────────────┴──────────────┴──────────────┴────────────────────────┴────────────────────┘ + # dataframe.loc[ + # ( + # (dataframe["sma48_deriv1"] <= 0) + # & (dataframe['min12_1h'].shift(36) == dataframe['min12_1h']) + # & (dataframe["sma12_deriv1"] > dataframe["sma12_deriv1"].shift(1)) + # ), ['enter_long', 'enter_tag']] = (1, 'min12_1h') + + # Backtested 2025-04-09 00:00:00 -> 2025-05-25 00:00:00 | Max open trades : 1 + # ┏━━━━━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┓ + # ┃ Strategy ┃ Trades ┃ Avg Profit % ┃ Tot Profit USDC ┃ Tot Profit % ┃ Avg Duration ┃ Win Draw Loss Win% ┃ Drawdown ┃ + # ┡━━━━━━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━┩ + # │ Zeus_8_3_2_B_4_2 │ 28 │ 0.49 │ 136.071 │ 13.61 │ 1 day, 2:11:00 │ 27 0 1 96.4 │ 37.149 USDC 3.17% │ + # └──────────────────┴────────┴──────────────┴─────────────────┴──────────────┴────────────────┴────────────────────────┴────────────────────┘ + # dataframe.loc[ + # ( + # (dataframe['sma5_inv_bas_1h'] == -1) + # ), ['enter_long', 'enter_tag']] = (1, 'sma5_inv') + + # Backtested 2025-04-09 00:00:00 -> 2025-05-25 00:00:00 | Max open trades : 1 + # ┏━━━━━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┓ + # ┃ Strategy ┃ Trades ┃ Avg Profit % ┃ Tot Profit USDC ┃ Tot Profit % ┃ Avg Duration ┃ Win Draw Loss Win% ┃ Drawdown ┃ + # ┡━━━━━━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━┩ + # │ Zeus_8_3_2_B_4_2 │ 32 │ 0.60 │ 191.920 │ 19.19 │ 20:56:00 │ 26 0 6 81.2 │ 48.62 USDC 4.02% │ + # └──────────────────┴────────┴──────────────┴─────────────────┴──────────────┴──────────────┴────────────────────────┴───────────────────┘ + dataframe.loc[ ( - # (dataframe["mid_smooth_5_deriv1_1d"] > 0) - # (dataframe["sma48_deriv2"] > 0) - (dataframe['sma48'] > dataframe['sma48'].shift(1) + 20) - ), ['enter_long', 'enter_tag']] = (1, 'sma48') + (dataframe['sma5_inv_bas_1h'] == -1) + & (dataframe['sma24_1h'].shift(1) <= dataframe['sma24_1h']) + ), ['enter_long', 'enter_tag']] = (1, 'sma5_inv') - dataframe['test'] = np.where(dataframe['enter_long'] == 1, dataframe['close'] * 1.01, np.nan) + dataframe.loc[ + ( + (dataframe['sma48_inv_bas'] == -1) + & (dataframe['sma24_1h'].shift(1) <= dataframe['sma24_1h']) + ), ['enter_long', 'enter_tag']] = (1, 'sma48_inv') + + + # dataframe['test'] = np.where(dataframe['enter_long'] == 1, dataframe['close'] * 1.01, np.nan) if self.dp.runmode.value in ('backtest'): dataframe.to_feather(f"user_data/backtest_results/{metadata['pair'].replace('/', '_')}_df.feather") @@ -2175,15 +2384,13 @@ class Zeus_8_3_2_B_4_2(IStrategy): if not self.should_enter_trade(pair, last_candle, current_time): return None - condition = (last_candle['enter_long'] and last_candle['sma60_deriv1'] > 0 and last_candle['hapercent'] > 0 and last_candle['stop_buying'] == False) \ - or last_candle['enter_tag'] == 'pct3' \ - or last_candle['enter_tag'] == 'pct3_1h' + condition = last_candle['sma48'] > before_last_candle['sma48'] + 20 # if (self.getShortName(pair) != 'BTC' and count_of_buys > 3): # condition = before_last_candle_24['mid_smooth_3_1h'] > before_last_candle_12['mid_smooth_3_1h'] and before_last_candle_12['mid_smooth_3_1h'] < last_candle['mid_smooth_3_1h'] #and last_candle['mid_smooth_3_deriv1_1h'] < -1.5 limit_buy = 40 - if (count_of_buys < limit_buy) and condition and (pct_max < lim): + if (count_of_buys < limit_buy) and condition and (pct_max < lim) and hours < 12: try: if self.pairs[pair]['has_gain'] and profit > 0: @@ -2341,90 +2548,21 @@ class Zeus_8_3_2_B_4_2(IStrategy): return val def adjust_stake_amount(self, pair: str, last_candle: DataFrame): - # Calculer le minimum des 14 derniers jours - nb_pairs = len(self.dp.current_whitelist()) + amount = self.config.get('stake_amount') - return self.config.get('stake_amount') - base_stake_amount = self.config.get('stake_amount') / (self.mises.value) # * nb_pairs) # Montant de base configuré - # pct60 = round(100 * self.getPctClose60D(pair, last_candle), 2) - - if True: # not pair in ('BTC/USDT', 'BTC/USDC'): - # factors = [1, 1.2, 1.3, 1.4] - if self.pairs[pair]['count_of_buys'] == 0: - # pctClose60 = self.getPctClose60D(pair, last_candle) - # dist_max = self.getDistMax(last_candle, pair) - # factor = self.multi_step_interpolate(dist_max, self.thresholds, self.factors) - factor = 1 #65 / min(65, last_candle['rsi_1d']) - if last_candle['slope_norm_1d'] < last_candle['slope_norm_1h']: - factor = 2 - - adjusted_stake_amount = max(base_stake_amount / 5, base_stake_amount * factor) - else: - adjusted_stake_amount = self.pairs[pair]['first_amount'] - else: - first_price = self.pairs[pair]['first_buy'] - if (first_price == 0): - first_price = last_candle['close'] - - last_max = last_candle['max12_1d'] - pct = 5 - if last_max > 0: - pct = 100 * (last_max - first_price) / last_max - - factor = self.multi_step_interpolate(pct, self.thresholds, self.factors) - adjusted_stake_amount = base_stake_amount * factor # max(base_stake_amount, min(100, base_stake_amount * percent_4)) - - # pct = 100 * abs(self.getPctFirstBuy(pair, last_candle)) + # state = int(last_candle.get('trend_class_1h')) # - # factor = self.multi_step_interpolate(pct, self.thresholds, self.factors) + # if state is not None: + # prefix = "bm" if state < 0 else "bp" + # attr_name = f"{prefix}{abs(state)}" + # if hasattr(self, attr_name): + # amount = getattr(self, attr_name).value + # else: + # amount = self.config.get('stake_amount') + # else: + # amount = self.config.get('stake_amount') - if self.pairs[pair]['count_of_buys'] == 0: - self.pairs[pair]['first_amount'] = adjusted_stake_amount - - return adjusted_stake_amount - - def calculateAmountSliding(self, pair, last_candle): - val = last_candle['close'] - min_sliding = min(last_candle['min60_1d'], val) - max_sliding = max(last_candle['max60_1d'], val) - min_abs = self.pairs[pair]['last_min'] - max_abs = self.pairs[pair]['last_max'] - full = self.wallets.get_total_stake_amount() - stake = full / self.stakes - - out_min = stake / 2 - out_max = stake * 2 - # Clamp sliding range within absolute bounds - min_sliding = max(min_sliding, min_abs) - max_sliding = min(max_sliding, max_abs) - - # Avoid division by zero - if max_sliding == min_sliding: - return out_max # Or midpoint, or default value - - # Inverse linear interpolation - position = (val - min_sliding) / (max_sliding - min_sliding) - return out_max - position * (out_max - out_min) - - def calculatePctSliding(self, pair, last_candle): - val = last_candle['close'] - min_sliding = last_candle['min60_1d'] - max_sliding = last_candle['max60_1d'] - min_abs = self.pairs[pair]['last_min'] - max_abs = self.pairs[pair]['last_max'] - out_min = 0.025 - out_max = 0.08 - # Clamp sliding range within absolute bounds - min_sliding = max(min_sliding, min_abs) - max_sliding = min(max_sliding, max_abs) - - # Avoid division by zero - if max_sliding == min_sliding: - return out_max # Or midpoint, or default value - - # Inverse linear interpolation - position = (val - min_sliding) / (max_sliding - min_sliding) - return out_max - position * (out_max - out_min) + return min(amount, self.wallets.get_available_stake_amount()) def expectedProfit(self, pair: str, last_candle: DataFrame): lim = 0.01 @@ -2630,50 +2768,50 @@ class Zeus_8_3_2_B_4_2(IStrategy): # Approximation directe (aucune interpolation complexe ici, juste une lecture) return numeric_matrice[row_idx, col_idx] - @property - def protections(self): - return [ - { - "method": "CooldownPeriod", - "stop_duration_candles": 12 - } - # { - # "method": "MaxDrawdown", - # "lookback_period_candles": self.lookback.value, - # "trade_limit": self.trade_limit.value, - # "stop_duration_candles": self.protection_stop.value, - # "max_allowed_drawdown": self.protection_max_allowed_dd.value, - # "only_per_pair": False - # }, - # { - # "method": "StoplossGuard", - # "lookback_period_candles": 24, - # "trade_limit": 4, - # "stop_duration_candles": self.protection_stoploss_stop.value, - # "only_per_pair": False - # }, - # { - # "method": "StoplossGuard", - # "lookback_period_candles": 24, - # "trade_limit": 4, - # "stop_duration_candles": 2, - # "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 - # } - ] + # @property + # def protections(self): + # return [ + # { + # "method": "CooldownPeriod", + # "stop_duration_candles": 12 + # } + # # { + # # "method": "MaxDrawdown", + # # "lookback_period_candles": self.lookback.value, + # # "trade_limit": self.trade_limit.value, + # # "stop_duration_candles": self.protection_stop.value, + # # "max_allowed_drawdown": self.protection_max_allowed_dd.value, + # # "only_per_pair": False + # # }, + # # { + # # "method": "StoplossGuard", + # # "lookback_period_candles": 24, + # # "trade_limit": 4, + # # "stop_duration_candles": self.protection_stoploss_stop.value, + # # "only_per_pair": False + # # }, + # # { + # # "method": "StoplossGuard", + # # "lookback_period_candles": 24, + # # "trade_limit": 4, + # # "stop_duration_candles": 2, + # # "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 conditional_smoothing(self, series, threshold=0.002): smoothed = [series.iloc[0]] @@ -2749,112 +2887,6 @@ class Zeus_8_3_2_B_4_2(IStrategy): return paliers - # def get_dca_stakes(self, - # max_drawdown: float = 0.65, - # base_stake: float = 100.0, - # first_steps: list[float] = [0.01, 0.01, 0.015, 0.015], - # growth: float = 1.2, - # stake_growth: float = 1.15 - # ) -> list[tuple[float, float]]: - # """ - # Génère les paliers de drawdown et leurs stakes associés. - # - # :param max_drawdown: Maximum drawdown (ex: 0.65 pour -65%) - # :param base_stake: Mise initiale - # :param first_steps: Paliers de départ (plus resserrés) - # :param growth: Multiplicateur d'espacement des paliers - # :param stake_growth: Croissance multiplicative des mises - # :return: Liste de tuples (palier_pct, stake) - # [(-0.01, 100.0), (-0.02, 115.0), (-0.035, 132.25), (-0.05, 152.09), (-0.068, 174.9), - # (-0.0896, 201.14), (-0.1155, 231.31), (-0.1466, 266.0), (-0.1839, 305.9), (-0.2287, 351.79), - # (-0.2825, 404.56), (-0.347, 465.24), (-0.4244, 535.03), (-0.5173, 615.28), (-0.6287, 707.57)] - # """ - # paliers = [ - # (-0.01, 100.0), (-0.02, 115.0), (-0.035, 130), (-0.05, 150), (-0.07, 150), - # (-0.10, 150), (-0.15, 150), (-0.20, 150), (-0.25, 150), - # (-0.30, 200), (-0.40, 200), - # (-0.50, 300), (-0.60, 400), (-0.70, 500), (-0.80, 1000) - # ] - # - # # cumulated = 0.0 - # # stake = base_stake - # # - # # # Étapes initiales - # # for step in first_steps: - # # cumulated += step - # # paliers.append((round(-cumulated, 4), round(stake, 2))) - # # stake *= stake_growth - # # - # # # Étapes suivantes - # # step = first_steps[-1] - # # while cumulated < max_drawdown: - # # step *= growth - # # cumulated += step - # # if cumulated >= max_drawdown: - # # break - # # paliers.append((round(-cumulated, 4), round(stake, 2))) - # # stake *= stake_growth - # - # return paliers - - # def get_active_stake(self, pct: float) -> float: - # """ - # Renvoie la mise correspondant au drawdown `pct`. - # - # :param pct: drawdown courant (négatif, ex: -0.043) - # :param paliers: liste de tuples (drawdown, stake) - # :return: stake correspondant - # """ - # abs_pct = abs(pct) - # stake = self.paliers[0][1] # stake par défaut - # - # for palier, s in self.paliers: - # if abs_pct >= abs(palier): - # stake = s - # else: - # break - # - # return stake - - # def get_palier_index(self, pct): - # """ - # Retourne l'index du palier franchi pour un pourcentage de baisse donné (pct). - # On cherche le palier le plus profond atteint (dernier franchi). - # """ - # for i in reversed(range(len(self.paliers))): - # seuil, _ = self.paliers[i] - # #print(f"pct={pct} seuil={seuil}") - # if pct <= seuil: - # # print(pct) - # return i - # return None # Aucun palier atteint - - # def poly_regression_predictions(self, series: pd.Series, window: int = 20, degree: int = 2, n_future: int = 3) -> pd.DataFrame: - # """ - # Renvoie une DataFrame avec `n_future` colonnes contenant les extrapolations des n prochains points - # selon une régression polynomiale ajustée sur les `window` dernières valeurs. - # """ - # result = pd.DataFrame(index=series.index) - # x = np.arange(window) - # - # for future_step in range(1, n_future + 1): - # result[f'poly_pred_t+{future_step}'] = np.nan - # - # for i in range(window - 1, len(series)): - # y = series.iloc[i - window + 1 : i + 1].values - # - # if np.any(pd.isna(y)): - # continue - # - # coeffs = np.polyfit(x, y, degree) - # poly = np.poly1d(coeffs) - # - # for future_step in range(1, n_future + 1): - # future_x = window - 1 + future_step # Extrapolation point - # result.loc[series.index[i], f'poly_pred_t+{future_step}'] = poly(future_x) - # - # return result - def polynomial_forecast(self, series: pd.Series, window: int = 20, degree: int = 2, steps=[12, 24, 36]): """ Calcule une régression polynomiale sur les `window` dernières valeurs de la série, @@ -2951,7 +2983,7 @@ class Zeus_8_3_2_B_4_2(IStrategy): # return last_candle['slope_norm_1d'] < last_candle['slope_norm_1h'] - if self.pairs[pair]['stop'] and last_candle['max_rsi_12_1h'] <= 60 and last_candle['trend_class_1h'] == -1: + if self.pairs[pair]['stop'] and last_candle['sma24_inv_bas_1h'] == -1: dispo = round(self.wallets.get_available_stake_amount()) self.pairs[pair]['stop'] = False self.log_trade( @@ -2966,7 +2998,7 @@ class Zeus_8_3_2_B_4_2(IStrategy): buys=self.pairs[pair]['count_of_buys'], stake=0 ) - + return True # 🟢 Dérivée 1 > 0 et dérivée 2 > 0: tendance haussière qui s’accé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 baissière qui s’accélère. @@ -2992,26 +3024,24 @@ class Zeus_8_3_2_B_4_2(IStrategy): # stake=0 # ) # else: - # if self.pairs[pair]['stop'] == False \ - # and last_candle[f"{self.indic_1d_p.value}_deriv1_1h"] <= self.indic_deriv1_1d_p_stop.value \ - # and last_candle[f"{self.indic_1d_p.value}_deriv2_1h"] <= self.indic_deriv2_1d_p_stop.value: - # self.pairs[pair]['stop'] = True - # # if self.pairs[pair]['current_profit'] > 0: - # # self.pairs[pair]['force_sell'] = True - # self.log_trade( - # last_candle=last_candle, - # date=current_time, - # action="🔴STOP", - # dispo=dispo, - # pair=pair, - # rate=last_candle['close'], - # trade_type='', - # profit=self.pairs[pair]['current_profit'], - # buys=self.pairs[pair]['count_of_buys'], - # stake=0 - # ) - # return False - # if self.pairs[pair]['stop']: + # if self.pairs[pair]['stop']: # \ + # # and last_candle[f"{self.indic_1d_p.value}_deriv1_1h"] <= self.indic_deriv1_1d_p_stop.value \ + # # and last_candle[f"{self.indic_1d_p.value}_deriv2_1h"] <= self.indic_deriv2_1d_p_stop.value: + # # self.pairs[pair]['stop'] = True + # # if self.pairs[pair]['current_profit'] > 0: + # # self.pairs[pair]['force_sell'] = True + # self.log_trade( + # last_candle=last_candle, + # date=current_time, + # action="🔴STOP " + str(last_candle['sma24_inv_bas_1h']), + # dispo=dispo, + # pair=pair, + # rate=last_candle['close'], + # trade_type='', + # profit=self.pairs[pair]['current_profit'], + # buys=self.pairs[pair]['count_of_buys'], + # stake=0 + # ) # return False return True @@ -3100,18 +3130,6 @@ class Zeus_8_3_2_B_4_2(IStrategy): else: return True - @staticmethod - def check_derivatives_vectorized(dataframe, deriv_pairs, thresholds): - """ - Retourne True si toutes les dérivées respectent leur seuil. - """ - mask = pd.Series(True, index=dataframe.index) - for d1_col, d2_col in deriv_pairs: - d1_thresh = thresholds.get(d1_col, 0) - d2_thresh = thresholds.get(d2_col, 0) - mask &= (dataframe[d1_col] >= d1_thresh) & (dataframe[d2_col] >= d2_thresh) - return mask - # ---------------------------------------------------------------------------------------------- # fallback defaults (used when no JSON exists) PARAMS_DIR = 'params' @@ -3158,8 +3176,8 @@ class Zeus_8_3_2_B_4_2(IStrategy): # --- classification dynamique via quantiles --- - q = df['slope_norm'].quantile([0.125, 0.375, 0.625, 0.875]).values - q1, q2, q3, q4 = q + q = df['slope_norm'].quantile([0.125, 0.250, 0.375, 0.5, 0.625, 0.875]).values + q1, q2, q3, q4 ,q5, q6 = q def classify_expanding(series): trend_class = [] @@ -3169,15 +3187,19 @@ class Zeus_8_3_2_B_4_2(IStrategy): q1, q2, q3, q4 = q v = series.iloc[i] if v <= q1: - trend_class.append(-2) + trend_class.append(-3) elif v <= q2: - trend_class.append(-1) + trend_class.append(-2) elif v <= q3: - trend_class.append(0) + trend_class.append(-1) elif v <= q4: + trend_class.append(0) + elif v <= q5: trend_class.append(1) - else: + elif v <= q6: trend_class.append(2) + else: + trend_class.append(3) return trend_class dataframe['slope_norm'] = df['slope_norm'] @@ -3187,131 +3209,6 @@ class Zeus_8_3_2_B_4_2(IStrategy): # Rolling sur la fenêtre passée dataframe['trend_class'] = classify_expanding(dataframe['slope_norm']) - - # # -------------------------- Trend detection (M2) -------------------------- - # def getTrend(self, dataframe: DataFrame) -> str: - # """ - # M2: SMA50 / SMA200 golden/death cross - # - bull: sma50 > sma200 - # - bear: sma50 < sma200 - # - range: sma50 ~= sma200 (within a small pct) - # - # Uses only past data (no future lookahead). - # """ - # if dataframe is None or len(dataframe) < max(self.DEFAULT_PARAMS['sma_short'], self.DEFAULT_PARAMS['sma_long']) + 2: - # return 'RANGE' - # - # sma_short = dataframe['close'].rolling(window=self.DEFAULT_PARAMS['sma_short']).mean() - # sma_long = dataframe['close'].rolling(window=self.DEFAULT_PARAMS['sma_long']).mean() - # - # cur_short = sma_short.iloc[-1] - # cur_long = sma_long.iloc[-1] - # - # # small relative threshold to avoid constant flips - # if cur_long == 0 or cur_short == 0: - # return 'RANGE' - # - # rel = abs(cur_short - cur_long) / cur_long - # threshold = 0.01 # 1% by default; tweak as needed - # - # if rel <= threshold: - # return 'RANGE' - # if cur_short > cur_long: - # return 'BULL' - # return 'BEAR' - - # # -------------------------- Parameter loading -------------------------- - # def loadParamsFor(self, pair: str, trend: str) -> dict: - # """Load JSON from params//.json with fallback to DEFAULT_PARAMS.""" - # pair_safe = pair.replace('/', '-') # folder name convention: BTC-USDT - # # cache key - # cache_key = f"{pair_safe}:{trend}" - # if cache_key in self._params_cache: - # return self._params_cache[cache_key] - # - # path = os.path.join(self.PARAMS_DIR, pair_safe, f"{trend}.json") - # if os.path.isfile(path): - # try: - # with open(path, 'r') as f: - # params = json.load(f) - # # merge with defaults so missing keys won't break - # merged = {**self.DEFAULT_PARAMS, **params} - # self._params_cache[cache_key] = merged - # logger.info(f"Loaded params for {pair} {trend} from {path}") - # return merged - # except Exception as e: - # logger.exception(f"Failed to load params {path}: {e}") - # - # # fallback - # logger.info(f"Using DEFAULT_PARAMS for {pair} {trend}") - # self._params_cache[cache_key] = dict(self.DEFAULT_PARAMS) - # return self._params_cache[cache_key] - - - def load_params_tree(self, base_path="user_data/strategies/params/"): - base = Path(base_path) - params_tree = {} - if not base.exists(): - raise FileNotFoundError(f"Base path '{base_path}' not found.") - - for pair_dir in base.iterdir(): - if not pair_dir.is_dir(): - continue - pair = self.getShortName(pair_dir.name) # ex : BTC-USDT - params_tree.setdefault(pair, {}) - - for trend_dir in pair_dir.iterdir(): - if not trend_dir.is_dir(): - continue - trend = trend_dir.name # ex : bull / bear / range - params_tree[pair].setdefault(trend, []) - - for file in trend_dir.glob("*-hyperopt_result.json"): - filename = file.name - - # Extraire START et END - try: - prefix = filename.replace("-hyperopt_result.json", "") - start, end = prefix.split("-", 1) # split en 2 - except Exception: - start = None - end = None - - # Lire le JSON - try: - with open(file, "r") as f: - content = json.load(f) - except Exception as err: - content = {"error": str(err)} - - params_tree[pair][trend].append({ - "start": start, - "end": end, - "file": str(file), - "content": content, - }) - for pair, trends in params_tree.items(): - for trend, entries in trends.items(): - if entries: - # indic_5m = self.getParamValue(pair, trend, 'buy', 'indic_5m') - # indic_deriv1_5m = self.getParamValue(pair, trend, 'buy', 'indic_deriv1_5m') - # indic_deriv2_5m = self.getParamValue(pair, trend, 'buy', 'indic_deriv2_5m') - # - # indic_5m_sell = self.getParamValue(pair, trend, 'sell', 'indic_5m_sell') - # indic_deriv1_5m_sell = self.getParamValue(pair, trend, 'sell', 'indic_deriv1_5m_sell') - # indic_deriv2_5m_sell = self.getParamValue(pair, trend, 'sell', 'indic_deriv2_5m_sell') - - print(f"{pair} -> {trend}") # {indic_5m} {indic_deriv1_5m} {indic_deriv2_5m} {indic_5m_sell} {indic_deriv1_5m_sell} {indic_deriv2_5m_sell}") - # for entry in entries: - # print(entry) - - return params_tree - - def getParamValue(self, pair, trend, space, param): - pair = self.getShortName(pair) - return self.parameters[pair][trend][0]['content']['params'][space][param] - - def select_uncorrelated_features(self, df, target, top_n=20, corr_threshold=0.7): """ Sélectionne les features les plus corrélées avec target, diff --git a/plots/Zeus_8_3_2_B_4_2/BTC/BTC_rf_model.pkl b/plots/Zeus_8_3_2_B_4_2/BTC/BTC_rf_model.pkl new file mode 100644 index 0000000..c04853f Binary files /dev/null and b/plots/Zeus_8_3_2_B_4_2/BTC/BTC_rf_model.pkl differ diff --git a/plots/Zeus_8_3_2_B_4_2/BTC/Courbe ROC.png b/plots/Zeus_8_3_2_B_4_2/BTC/Courbe ROC.png new file mode 100644 index 0000000..bba3d04 Binary files /dev/null and b/plots/Zeus_8_3_2_B_4_2/BTC/Courbe ROC.png differ diff --git a/plots/Zeus_8_3_2_B_4_2/BTC/Feature importances.png b/plots/Zeus_8_3_2_B_4_2/BTC/Feature importances.png new file mode 100644 index 0000000..8114de2 Binary files /dev/null and b/plots/Zeus_8_3_2_B_4_2/BTC/Feature importances.png differ diff --git a/plots/Zeus_8_3_2_B_4_2/BTC/Importance des features.png b/plots/Zeus_8_3_2_B_4_2/BTC/Importance des features.png new file mode 100644 index 0000000..64ebac5 Binary files /dev/null and b/plots/Zeus_8_3_2_B_4_2/BTC/Importance des features.png differ diff --git a/plots/Zeus_8_3_2_B_4_2/BTC/Matrice de confusion.png b/plots/Zeus_8_3_2_B_4_2/BTC/Matrice de confusion.png new file mode 100644 index 0000000..fee129c Binary files /dev/null and b/plots/Zeus_8_3_2_B_4_2/BTC/Matrice de confusion.png differ diff --git a/plots/Zeus_8_3_2_B_4_2/BTC/Matrice_de_correlation_temperature.png b/plots/Zeus_8_3_2_B_4_2/BTC/Matrice_de_correlation_temperature.png new file mode 100644 index 0000000..b0a77f9 Binary files /dev/null and b/plots/Zeus_8_3_2_B_4_2/BTC/Matrice_de_correlation_temperature.png differ diff --git a/plots/Zeus_8_3_2_B_4_2/BTC/PartialDependenceDisplay.png b/plots/Zeus_8_3_2_B_4_2/BTC/PartialDependenceDisplay.png new file mode 100644 index 0000000..9a3d2a3 Binary files /dev/null and b/plots/Zeus_8_3_2_B_4_2/BTC/PartialDependenceDisplay.png differ diff --git a/plots/Zeus_8_3_2_B_4_2/BTC/Permutation feature importance.png b/plots/Zeus_8_3_2_B_4_2/BTC/Permutation feature importance.png new file mode 100644 index 0000000..4b3b09c Binary files /dev/null and b/plots/Zeus_8_3_2_B_4_2/BTC/Permutation feature importance.png differ diff --git a/plots/Zeus_8_3_2_B_4_2/BTC/optimization_history.html b/plots/Zeus_8_3_2_B_4_2/BTC/optimization_history.html new file mode 100644 index 0000000..94e9369 --- /dev/null +++ b/plots/Zeus_8_3_2_B_4_2/BTC/optimization_history.html @@ -0,0 +1,3888 @@ + + + +
+
+ + \ No newline at end of file diff --git a/plots/Zeus_8_3_2_B_4_2/BTC/parallel_coordinates.html b/plots/Zeus_8_3_2_B_4_2/BTC/parallel_coordinates.html new file mode 100644 index 0000000..84018fb --- /dev/null +++ b/plots/Zeus_8_3_2_B_4_2/BTC/parallel_coordinates.html @@ -0,0 +1,3888 @@ + + + +
+
+ + \ No newline at end of file diff --git a/plots/Zeus_8_3_2_B_4_2/BTC/param_importances.html b/plots/Zeus_8_3_2_B_4_2/BTC/param_importances.html new file mode 100644 index 0000000..b03bb39 --- /dev/null +++ b/plots/Zeus_8_3_2_B_4_2/BTC/param_importances.html @@ -0,0 +1,3888 @@ + + + +
+
+ + \ No newline at end of file diff --git a/plots/Zeus_8_3_2_B_4_2/BTC/seuil_de_probabilite.png b/plots/Zeus_8_3_2_B_4_2/BTC/seuil_de_probabilite.png new file mode 100644 index 0000000..10a95e0 Binary files /dev/null and b/plots/Zeus_8_3_2_B_4_2/BTC/seuil_de_probabilite.png differ diff --git a/plots/Zeus_8_3_2_B_4_2/BTC/shap_force_plot.html b/plots/Zeus_8_3_2_B_4_2/BTC/shap_force_plot.html new file mode 100644 index 0000000..b5a8463 --- /dev/null +++ b/plots/Zeus_8_3_2_B_4_2/BTC/shap_force_plot.html @@ -0,0 +1,18 @@ + + +
+
+ Visualization omitted, Javascript library not loaded!
+ Have you run `initjs()` in this notebook? If this notebook was from another + user you must also trust this notebook (File -> Trust notebook). If you are viewing + this notebook on github the Javascript has been stripped for security. If you are using + JupyterLab this error is because a JupyterLab extension has not yet been written. +
+ diff --git a/plots/Zeus_8_3_2_B_4_2/BTC/shap_waterfall.png b/plots/Zeus_8_3_2_B_4_2/BTC/shap_waterfall.png new file mode 100644 index 0000000..6b7da3c Binary files /dev/null and b/plots/Zeus_8_3_2_B_4_2/BTC/shap_waterfall.png differ diff --git a/plots/Zeus_8_3_2_B_4_2/BTC/slice.html b/plots/Zeus_8_3_2_B_4_2/BTC/slice.html new file mode 100644 index 0000000..e3aa40a --- /dev/null +++ b/plots/Zeus_8_3_2_B_4_2/BTC/slice.html @@ -0,0 +1,3888 @@ + + + +
+
+ + \ No newline at end of file diff --git a/plots/Zeus_8_3_2_B_4_2/BTC/threshold_analysis.png b/plots/Zeus_8_3_2_B_4_2/BTC/threshold_analysis.png new file mode 100644 index 0000000..2466cc4 Binary files /dev/null and b/plots/Zeus_8_3_2_B_4_2/BTC/threshold_analysis.png differ