FrictradeLearning
This commit is contained in:
@@ -68,6 +68,15 @@ from sklearn.model_selection import train_test_split
|
|||||||
from sklearn.metrics import f1_score
|
from sklearn.metrics import f1_score
|
||||||
from xgboost import XGBClassifier
|
from xgboost import XGBClassifier
|
||||||
|
|
||||||
|
from sklearn.model_selection import train_test_split
|
||||||
|
from sklearn.linear_model import LogisticRegression
|
||||||
|
from sklearn.calibration import CalibratedClassifierCV
|
||||||
|
from sklearn.metrics import brier_score_loss, roc_auc_score
|
||||||
|
|
||||||
|
from sklearn.preprocessing import StandardScaler
|
||||||
|
from sklearn.pipeline import Pipeline
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Couleurs ANSI de base
|
# Couleurs ANSI de base
|
||||||
@@ -82,7 +91,7 @@ RESET = "\033[0m"
|
|||||||
|
|
||||||
class FrictradeLearning(IStrategy):
|
class FrictradeLearning(IStrategy):
|
||||||
startup_candle_count = 180
|
startup_candle_count = 180
|
||||||
|
train_model = None
|
||||||
model_indicators = []
|
model_indicators = []
|
||||||
DEFAULT_PARAMS = {
|
DEFAULT_PARAMS = {
|
||||||
"rsi_buy": 30,
|
"rsi_buy": 30,
|
||||||
@@ -97,6 +106,19 @@ class FrictradeLearning(IStrategy):
|
|||||||
"minimal_roi": {"0": 0.10}
|
"minimal_roi": {"0": 0.10}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dca_levels = {
|
||||||
|
0: 0.00,
|
||||||
|
-2: 0.05,
|
||||||
|
-4: 0.07,
|
||||||
|
-6: 0.10,
|
||||||
|
-8: 0.12,
|
||||||
|
-10: 0.15,
|
||||||
|
-12: 0.18,
|
||||||
|
-14: 0.22,
|
||||||
|
-16: 0.26,
|
||||||
|
-18: 0.30,
|
||||||
|
}
|
||||||
|
|
||||||
# ROI table:
|
# ROI table:
|
||||||
minimal_roi = {
|
minimal_roi = {
|
||||||
"0": 10
|
"0": 10
|
||||||
@@ -359,7 +381,7 @@ class FrictradeLearning(IStrategy):
|
|||||||
self.printLog(
|
self.printLog(
|
||||||
f"| {'Date':<16} | {'Action':<10} |{'Pair':<5}| {'Trade Type':<18} |{'Rate':>8} | {'Dispo':>6} | {'Profit':>8} "
|
f"| {'Date':<16} | {'Action':<10} |{'Pair':<5}| {'Trade Type':<18} |{'Rate':>8} | {'Dispo':>6} | {'Profit':>8} "
|
||||||
f"| {'Pct':>6} | {'max_touch':>11} | {'last_lost':>12} | {'last_max':>7}| {'last_min':>7}|{'Buys':>5}| {'Stake':>5} |"
|
f"| {'Pct':>6} | {'max_touch':>11} | {'last_lost':>12} | {'last_max':>7}| {'last_min':>7}|{'Buys':>5}| {'Stake':>5} |"
|
||||||
f"{'rsi':>6}" #|Distmax|s201d|s5_1d|s5_2d|s51h|s52h|smt1h|smt2h|tdc1d|tdc1h"
|
f"{'rsi':>6}|{'mlprob':>6}" #|Distmax|s201d|s5_1d|s5_2d|s51h|s52h|smt1h|smt2h|tdc1d|tdc1h"
|
||||||
)
|
)
|
||||||
self.printLineLog()
|
self.printLineLog()
|
||||||
df = pd.DataFrame.from_dict(self.pairs, orient='index')
|
df = pd.DataFrame.from_dict(self.pairs, orient='index')
|
||||||
@@ -410,8 +432,8 @@ class FrictradeLearning(IStrategy):
|
|||||||
f"|{color}{profit or '-':>10}{RESET}| {pct_max or '-':>6} | {round(self.pairs[pair]['max_touch'], 2) or '-':>11} | {last_lost or '-':>12} "
|
f"|{color}{profit or '-':>10}{RESET}| {pct_max or '-':>6} | {round(self.pairs[pair]['max_touch'], 2) or '-':>11} | {last_lost or '-':>12} "
|
||||||
f"| {last_max or '-':>7} | {last_min or '-':>7} |{total_counts or '-':>5}|{stake or '-':>7}"
|
f"| {last_max or '-':>7} | {last_min or '-':>7} |{total_counts or '-':>5}|{stake or '-':>7}"
|
||||||
f"{round(last_candle['max_rsi_24'], 1) or '-' :>6}|{round(last_candle['rsi_1h'], 1) or '-' :>6}|{round(last_candle['rsi_1d'], 1) or '-' :>6}|"
|
f"{round(last_candle['max_rsi_24'], 1) or '-' :>6}|{round(last_candle['rsi_1h'], 1) or '-' :>6}|{round(last_candle['rsi_1d'], 1) or '-' :>6}|"
|
||||||
f"{round(last_candle['rtp_1h'] * 100, 0) or '-' :>6}|{round(last_candle['rtp_1d'] * 100, 0) or '-' :>6}|"
|
# f"{round(last_candle['rtp_1h'] * 100, 0) or '-' :>6}|{round(last_candle['rtp_1d'] * 100, 0) or '-' :>6}|"
|
||||||
|
f"{round(last_candle['ml_prob'], 1) or '-' :>6}|"
|
||||||
)
|
)
|
||||||
|
|
||||||
def printLineLog(self):
|
def printLineLog(self):
|
||||||
@@ -445,6 +467,10 @@ class FrictradeLearning(IStrategy):
|
|||||||
dataframe['sma5'] = dataframe['mid'].ewm(span=5, adjust=False).mean() #dataframe["mid"].rolling(window=5).mean()
|
dataframe['sma5'] = dataframe['mid'].ewm(span=5, adjust=False).mean() #dataframe["mid"].rolling(window=5).mean()
|
||||||
dataframe['sma5_deriv1'] = 1000 * (dataframe['sma5'] - dataframe['sma5'].shift(1)) / dataframe['sma5'].shift(1)
|
dataframe['sma5_deriv1'] = 1000 * (dataframe['sma5'] - dataframe['sma5'].shift(1)) / dataframe['sma5'].shift(1)
|
||||||
|
|
||||||
|
dataframe['sma12'] = dataframe['mid'].ewm(span=12, adjust=False).mean()
|
||||||
|
dataframe['sma12_deriv1'] = 1000 * (dataframe['sma12'] - dataframe['sma12'].shift(1)) / dataframe[
|
||||||
|
'sma12'].shift(1)
|
||||||
|
|
||||||
dataframe['sma24'] = dataframe['mid'].ewm(span=24, adjust=False).mean()
|
dataframe['sma24'] = dataframe['mid'].ewm(span=24, adjust=False).mean()
|
||||||
dataframe['sma24_deriv1'] = 1000 * (dataframe['sma24'] - dataframe['sma24'].shift(1)) / dataframe['sma24'].shift(1)
|
dataframe['sma24_deriv1'] = 1000 * (dataframe['sma24'] - dataframe['sma24'].shift(1)) / dataframe['sma24'].shift(1)
|
||||||
|
|
||||||
@@ -477,16 +503,35 @@ class FrictradeLearning(IStrategy):
|
|||||||
dataframe['min180'] = talib.MIN(dataframe['mid'], timeperiod=180)
|
dataframe['min180'] = talib.MIN(dataframe['mid'], timeperiod=180)
|
||||||
dataframe['max180'] = talib.MAX(dataframe['mid'], timeperiod=180)
|
dataframe['max180'] = talib.MAX(dataframe['mid'], timeperiod=180)
|
||||||
dataframe['pct180'] = ((dataframe["mid"] - dataframe['min180'] ) / (dataframe['max180'] - dataframe['min180'] ))
|
dataframe['pct180'] = ((dataframe["mid"] - dataframe['min180'] ) / (dataframe['max180'] - dataframe['min180'] ))
|
||||||
|
|
||||||
dataframe = self.rsi_trend_probability(dataframe, short=60, long=360)
|
dataframe = self.rsi_trend_probability(dataframe, short=60, long=360)
|
||||||
|
|
||||||
# ################### INFORMATIVE 1h
|
# ################### INFORMATIVE 1h
|
||||||
informative = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe='1h')
|
informative = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe='1h')
|
||||||
informative['mid'] = informative['open'] + (informative['close'] - informative['open']) / 2
|
informative['mid'] = informative['open'] + (informative['close'] - informative['open']) / 2
|
||||||
# informative = self.populate1hIndicators(df=informative, metadata=metadata)
|
# Calcul MACD
|
||||||
|
macd, macdsignal, macdhist = talib.MACD(
|
||||||
|
informative['close'],
|
||||||
|
fastperiod=12,
|
||||||
|
slowperiod=26,
|
||||||
|
signalperiod=9
|
||||||
|
)
|
||||||
|
informative['macd'] = macd
|
||||||
|
informative['macdsignal'] = macdsignal
|
||||||
|
informative['macdhist'] = macdhist
|
||||||
|
|
||||||
|
informative['rsi'] = talib.RSI(informative['mid'], timeperiod=14)
|
||||||
|
informative['sma24'] = informative['mid'].ewm(span=24, adjust=False).mean()
|
||||||
|
informative['sma24_deriv1'] = 1000 * (informative['sma24'] - informative['sma24'].shift(1)) / informative['sma24'].shift(1)
|
||||||
|
|
||||||
|
informative['sma60'] = informative['mid'].ewm(span=60, adjust=False).mean()
|
||||||
|
informative['sma60_deriv1'] = 1000 * (informative['sma60'] - informative['sma60'].shift(1)) / informative['sma60'].shift(1)
|
||||||
informative['rsi'] = talib.RSI(informative['mid'], timeperiod=14)
|
informative['rsi'] = talib.RSI(informative['mid'], timeperiod=14)
|
||||||
self.calculeDerivees(informative, 'rsi', ema_period=12)
|
self.calculeDerivees(informative, 'rsi', ema_period=12)
|
||||||
informative = self.rsi_trend_probability(informative)
|
# informative = self.rsi_trend_probability(informative)
|
||||||
|
|
||||||
|
probas = self.calculModelInformative(informative)
|
||||||
|
|
||||||
|
# informative = self.populate1hIndicators(df=informative, metadata=metadata)
|
||||||
# informative = self.calculateRegression(informative, 'mid', lookback=15)
|
# informative = self.calculateRegression(informative, 'mid', lookback=15)
|
||||||
dataframe = merge_informative_pair(dataframe, informative, '1m', '1h', ffill=True)
|
dataframe = merge_informative_pair(dataframe, informative, '1m', '1h', ffill=True)
|
||||||
|
|
||||||
@@ -494,7 +539,7 @@ class FrictradeLearning(IStrategy):
|
|||||||
informative = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe='1d')
|
informative = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe='1d')
|
||||||
informative['mid'] = informative['open'] + (informative['close'] - informative['open']) / 2
|
informative['mid'] = informative['open'] + (informative['close'] - informative['open']) / 2
|
||||||
informative['rsi'] = talib.RSI(informative['mid'], timeperiod=5)
|
informative['rsi'] = talib.RSI(informative['mid'], timeperiod=5)
|
||||||
informative = self.rsi_trend_probability(informative)
|
# informative = self.rsi_trend_probability(informative)
|
||||||
# informative = self.calculateRegression(informative, 'mid', lookback=15)
|
# informative = self.calculateRegression(informative, 'mid', lookback=15)
|
||||||
dataframe = merge_informative_pair(dataframe, informative, '1m', '1d', ffill=True)
|
dataframe = merge_informative_pair(dataframe, informative, '1m', '1d', ffill=True)
|
||||||
|
|
||||||
@@ -545,7 +590,6 @@ class FrictradeLearning(IStrategy):
|
|||||||
)
|
)
|
||||||
dataframe["bb_width"] = (dataframe["bb_upperband"] - dataframe["bb_lowerband"]) / dataframe["sma24"]
|
dataframe["bb_width"] = (dataframe["bb_upperband"] - dataframe["bb_lowerband"]) / dataframe["sma24"]
|
||||||
|
|
||||||
|
|
||||||
# Calcul MACD
|
# Calcul MACD
|
||||||
macd, macdsignal, macdhist = talib.MACD(
|
macd, macdsignal, macdhist = talib.MACD(
|
||||||
dataframe['close'],
|
dataframe['close'],
|
||||||
@@ -637,10 +681,15 @@ class FrictradeLearning(IStrategy):
|
|||||||
).on_balance_volume()
|
).on_balance_volume()
|
||||||
self.calculeDerivees(dataframe, 'obv', ema_period=1)
|
self.calculeDerivees(dataframe, 'obv', ema_period=1)
|
||||||
|
|
||||||
dataframe['obv5'] = ta.volume.OnBalanceVolumeIndicator(
|
dataframe['obv12'] = ta.volume.OnBalanceVolumeIndicator(
|
||||||
close=dataframe['sma5'], volume=dataframe['volume'].rolling(5).sum()
|
close=dataframe['sma12'], volume=dataframe['volume'].rolling(12).sum()
|
||||||
).on_balance_volume()
|
).on_balance_volume()
|
||||||
self.calculeDerivees(dataframe, 'obv5', ema_period=5)
|
|
||||||
|
dataframe['obv24'] = ta.volume.OnBalanceVolumeIndicator(
|
||||||
|
close=dataframe['sma24'], volume=dataframe['volume'].rolling(24).sum()
|
||||||
|
).on_balance_volume()
|
||||||
|
|
||||||
|
# self.calculeDerivees(dataframe, 'obv5', ema_period=5)
|
||||||
|
|
||||||
# --- Volatilité récente (écart-type des rendements) ---
|
# --- Volatilité récente (écart-type des rendements) ---
|
||||||
dataframe['vol_24'] = dataframe['percent'].rolling(24).std()
|
dataframe['vol_24'] = dataframe['percent'].rolling(24).std()
|
||||||
@@ -674,13 +723,23 @@ class FrictradeLearning(IStrategy):
|
|||||||
self.trainModel(dataframe, metadata)
|
self.trainModel(dataframe, metadata)
|
||||||
|
|
||||||
short_pair = self.getShortName(pair)
|
short_pair = self.getShortName(pair)
|
||||||
|
path=f"user_data/plots/{short_pair}/"
|
||||||
|
|
||||||
self.model = joblib.load(f"{short_pair}_rf_model.pkl")
|
self.model = joblib.load(f"{path}/{short_pair}_rf_model.pkl")
|
||||||
|
|
||||||
# Préparer les features pour la prédiction
|
# Préparer les features pour la prédiction
|
||||||
features = dataframe[self.model_indicators].fillna(0)
|
features = dataframe[self.model_indicators].fillna(0)
|
||||||
|
|
||||||
# Prédiction : probabilité que le prix monte
|
# 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]
|
probs = self.model.predict_proba(features)[:, 1]
|
||||||
|
|
||||||
# Sauvegarder la probabilité pour l’analyse
|
# Sauvegarder la probabilité pour l’analyse
|
||||||
@@ -765,7 +824,7 @@ class FrictradeLearning(IStrategy):
|
|||||||
|
|
||||||
# Buy = prediction > threshold
|
# Buy = prediction > threshold
|
||||||
dataframe["buy"] = 0
|
dataframe["buy"] = 0
|
||||||
dataframe.loc[dataframe["ml_prob"] > threshold, ['enter_long', 'enter_tag']] = (1, f"future")
|
dataframe.loc[dataframe["ml_prob"] > 0.5, ['enter_long', 'enter_tag']] = (1, f"future")
|
||||||
dataframe['test'] = np.where(dataframe['enter_long'] == 1, dataframe['close'] * 1.003, np.nan)
|
dataframe['test'] = np.where(dataframe['enter_long'] == 1, dataframe['close'] * 1.003, np.nan)
|
||||||
|
|
||||||
return dataframe
|
return dataframe
|
||||||
@@ -883,6 +942,9 @@ class FrictradeLearning(IStrategy):
|
|||||||
|
|
||||||
def adjust_stake_amount(self, pair: str, last_candle: DataFrame):
|
def adjust_stake_amount(self, pair: str, last_candle: DataFrame):
|
||||||
|
|
||||||
|
if self.pairs[pair]['first_amount'] > 0:
|
||||||
|
return self.pairs[pair]['first_amount']
|
||||||
|
|
||||||
ath = max(self.pairs[pair]['last_max'], self.get_last_ath_before_candle(last_candle))
|
ath = max(self.pairs[pair]['last_max'], self.get_last_ath_before_candle(last_candle))
|
||||||
|
|
||||||
ath_dist = 100 * (ath - last_candle["mid"]) / ath
|
ath_dist = 100 * (ath - last_candle["mid"]) / ath
|
||||||
@@ -967,7 +1029,39 @@ class FrictradeLearning(IStrategy):
|
|||||||
# stake_amount = last_amount * current_rate * 0.5
|
# stake_amount = last_amount * current_rate * 0.5
|
||||||
# return stake_amount
|
# return stake_amount
|
||||||
|
|
||||||
condition = last_candle['hapercent'] > 0 and last_candle['sma24_deriv1'] > 0
|
|
||||||
|
########################### ALGO ATH
|
||||||
|
# # --- 1. Calcul ATH local de la paire ---
|
||||||
|
# ath = max(self.pairs[pair]['last_max'], self.get_last_ath_before_candle(last_candle))
|
||||||
|
#
|
||||||
|
# # --- 2. Distance ATH - current ---
|
||||||
|
# dd = (current_rate - ath) / ath * 100 # dd <= 0
|
||||||
|
#
|
||||||
|
# if dd > -1: # pas de renfort si drawdown trop faible
|
||||||
|
# return None
|
||||||
|
#
|
||||||
|
# # --- 3. DCA dynamique (modèle exponentiel) ---
|
||||||
|
# a = 0.015
|
||||||
|
# b = 0.12
|
||||||
|
#
|
||||||
|
# pct = a * (math.exp(b * (-dd)) - 1) # proportion du wallet libre
|
||||||
|
#
|
||||||
|
# # Clamp de sécurité
|
||||||
|
# pct = min(max(pct, 0), 0.35) # max 35% d’un coup
|
||||||
|
#
|
||||||
|
# if pct <= 0:
|
||||||
|
# return None
|
||||||
|
#
|
||||||
|
# # --- 4. Stake en fonction du wallet libre ---
|
||||||
|
# stake_amount = self.wallets.get_available_stake_amount() * pct
|
||||||
|
#
|
||||||
|
# if stake_amount < self.min_stake_amount:
|
||||||
|
# return None
|
||||||
|
|
||||||
|
# FIN ########################## ALGO ATH
|
||||||
|
|
||||||
|
condition = last_candle['hapercent'] > 0 and last_candle['sma24_deriv1'] > 0 \
|
||||||
|
and last_candle['close'] < self.pairs[pair]['first_buy'] and last_candle['ml_prob'] > 0.65
|
||||||
limit_buy = 40
|
limit_buy = 40
|
||||||
# or (last_candle['close'] <= last_candle['min180'] and hours > 3)
|
# or (last_candle['close'] <= last_candle['min180'] and hours > 3)
|
||||||
if (decline >= dca_threshold) and condition:
|
if (decline >= dca_threshold) and condition:
|
||||||
@@ -977,9 +1071,7 @@ class FrictradeLearning(IStrategy):
|
|||||||
self.pairs[pair]['previous_profit'] = profit
|
self.pairs[pair]['previous_profit'] = profit
|
||||||
return None
|
return None
|
||||||
|
|
||||||
max_amount = self.config.get('stake_amount') * 2.5
|
stake_amount = min(self.wallets.get_available_stake_amount(), self.adjust_stake_amount(pair, last_candle) / 2)
|
||||||
stake_amount = min(min(max_amount, self.wallets.get_available_stake_amount()),
|
|
||||||
self.adjust_stake_amount(pair, last_candle))
|
|
||||||
# print(f"profit={profit} previous={self.pairs[pair]['previous_profit']} count_of_buys={trade.nr_of_successful_entries}")
|
# print(f"profit={profit} previous={self.pairs[pair]['previous_profit']} count_of_buys={trade.nr_of_successful_entries}")
|
||||||
if stake_amount > 0:
|
if stake_amount > 0:
|
||||||
self.pairs[pair]['previous_profit'] = profit
|
self.pairs[pair]['previous_profit'] = profit
|
||||||
@@ -1143,7 +1235,7 @@ class FrictradeLearning(IStrategy):
|
|||||||
stake=0
|
stake=0
|
||||||
)
|
)
|
||||||
|
|
||||||
if last_candle['ml_prob'] > 0.5:
|
if last_candle['ml_prob'] > 0.65:
|
||||||
return None
|
return None
|
||||||
# if last_candle['sma24_deriv1'] > 0 : #and minutes < 180 and baisse < 30: # and last_candle['sma5_deriv1'] > -0.15:
|
# if last_candle['sma24_deriv1'] > 0 : #and minutes < 180 and baisse < 30: # and last_candle['sma5_deriv1'] > -0.15:
|
||||||
# if (minutes < 180):
|
# if (minutes < 180):
|
||||||
@@ -1416,19 +1508,34 @@ class FrictradeLearning(IStrategy):
|
|||||||
# study.optimize(objective, n_trials=50)
|
# study.optimize(objective, n_trials=50)
|
||||||
|
|
||||||
def objective(trial):
|
def objective(trial):
|
||||||
self.train_model = XGBClassifier(
|
# local_model = XGBClassifier(
|
||||||
n_estimators=trial.suggest_int("n_estimators", 200, 800),
|
# n_estimators=300, # nombre d'arbres plus raisonnable
|
||||||
max_depth=trial.suggest_int("max_depth", 3, 10),
|
# learning_rate=0.01, # un peu plus rapide que 0.006, mais stable
|
||||||
learning_rate=trial.suggest_float("learning_rate", 0.005, 0.3, log=True),
|
# max_depth=4, # capture plus de patterns que 3, sans overfitting excessif
|
||||||
subsample=trial.suggest_float("subsample", 0.6, 1.0),
|
# subsample=0.7, # utilise 70% des lignes pour chaque arbre → réduit overfitting
|
||||||
colsample_bytree=trial.suggest_float("colsample_bytree", 0.6, 1.0),
|
# 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=300, #trial.suggest_int("n_estimators", 300, 500),
|
||||||
|
max_depth=trial.suggest_int("max_depth", 1, 3),
|
||||||
|
learning_rate=0.01, #trial.suggest_float("learning_rate", 0.005, 0.3, log=True),
|
||||||
|
subsample=0.7, #trial.suggest_float("subsample", 0.6, 1.0),
|
||||||
|
colsample_bytree=0.8, #trial.suggest_float("colsample_bytree", 0.6, 1.0),
|
||||||
scale_pos_weight=1,
|
scale_pos_weight=1,
|
||||||
objective="binary:logistic",
|
objective="binary:logistic",
|
||||||
eval_metric="logloss",
|
eval_metric="logloss",
|
||||||
n_jobs=-1
|
n_jobs=-1
|
||||||
)
|
)
|
||||||
|
|
||||||
self.train_model.fit(
|
local_model.fit(
|
||||||
X_train,
|
X_train,
|
||||||
y_train,
|
y_train,
|
||||||
eval_set=[(X_valid, y_valid)],
|
eval_set=[(X_valid, y_valid)],
|
||||||
@@ -1436,14 +1543,63 @@ class FrictradeLearning(IStrategy):
|
|||||||
verbose=False
|
verbose=False
|
||||||
)
|
)
|
||||||
|
|
||||||
proba = self.train_model.predict_proba(X_valid)[:, 1]
|
proba = local_model.predict_proba(X_valid)[:, 1]
|
||||||
thresholds = np.linspace(0.1, 0.9, 50)
|
thresholds = np.linspace(0.1, 0.9, 50)
|
||||||
best_f1 = max(f1_score(y_valid, (proba > t)) for t in thresholds)
|
best_f1 = max(f1_score(y_valid, (proba > t)) for t in thresholds)
|
||||||
|
|
||||||
return best_f1
|
return best_f1
|
||||||
study = optuna.create_study(direction="maximize")
|
study = optuna.create_study(direction="maximize")
|
||||||
study.optimize(objective, n_trials=50)
|
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"{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 ------
|
# ---- après avoir exécuté la study ------
|
||||||
|
|
||||||
print("Best value (F1):", study.best_value)
|
print("Best value (F1):", study.best_value)
|
||||||
@@ -1540,9 +1696,16 @@ class FrictradeLearning(IStrategy):
|
|||||||
force_plot = shap.force_plot(explainer.expected_value, shap_values[0, :], X_valid.iloc[0, :])
|
force_plot = shap.force_plot(explainer.expected_value, shap_values[0, :], X_valid.iloc[0, :])
|
||||||
shap.save_html(f"{path}/shap_force_plot.html", force_plot)
|
shap.save_html(f"{path}/shap_force_plot.html", force_plot)
|
||||||
|
|
||||||
PartialDependenceDisplay.from_estimator(self.train_model, X_valid, selected_features, kind='average')
|
fig, ax = plt.subplots(figsize=(24, 48))
|
||||||
plt.figure(figsize=(24, 24))
|
PartialDependenceDisplay.from_estimator(
|
||||||
plt.savefig(f"{path}/PartialDependenceDisplay.png", bbox_inches='tight')
|
self.train_model,
|
||||||
|
X_valid,
|
||||||
|
selected_features,
|
||||||
|
kind="average",
|
||||||
|
ax=ax
|
||||||
|
)
|
||||||
|
fig.savefig(f"{path}/PartialDependenceDisplay.png", bbox_inches="tight")
|
||||||
|
plt.close(fig)
|
||||||
|
|
||||||
best_f1 = 0
|
best_f1 = 0
|
||||||
best_t = 0.5
|
best_t = 0.5
|
||||||
@@ -1562,9 +1725,10 @@ class FrictradeLearning(IStrategy):
|
|||||||
print(f"Accuracy: {acc:.3f}")
|
print(f"Accuracy: {acc:.3f}")
|
||||||
|
|
||||||
# 7️⃣ Sauvegarde du modèle
|
# 7️⃣ Sauvegarde du modèle
|
||||||
joblib.dump(self.train_model, f"{pair}_rf_model.pkl")
|
joblib.dump(self.train_model, f"{path}/{pair}_rf_model.pkl")
|
||||||
print(f"✅ Modèle sauvegardé sous {pair}_rf_model.pkl")
|
print(f"✅ Modèle sauvegardé sous {pair}_rf_model.pkl")
|
||||||
|
|
||||||
|
|
||||||
# X = dataframe des features (après shift/rolling/indicators)
|
# X = dataframe des features (après shift/rolling/indicators)
|
||||||
# y = target binaire ou décimale
|
# y = target binaire ou décimale
|
||||||
# model = ton modèle entraîné (RandomForestClassifier ou Regressor)
|
# model = ton modèle entraîné (RandomForestClassifier ou Regressor)
|
||||||
@@ -1666,8 +1830,7 @@ class FrictradeLearning(IStrategy):
|
|||||||
Analyse complète d'un modèle ML supervisé (classification binaire).
|
Analyse complète d'un modèle ML supervisé (classification binaire).
|
||||||
Affiche performances, importance des features, matrices, seuils, etc.
|
Affiche performances, importance des features, matrices, seuils, etc.
|
||||||
"""
|
"""
|
||||||
output_dir = f"user_data/plots/{pair}/"
|
os.makedirs(self.path, exist_ok=True)
|
||||||
os.makedirs(output_dir, exist_ok=True)
|
|
||||||
|
|
||||||
# ---- Prédictions ----
|
# ---- Prédictions ----
|
||||||
preds = model.predict(X_valid)
|
preds = model.predict(X_valid)
|
||||||
@@ -1704,7 +1867,7 @@ class FrictradeLearning(IStrategy):
|
|||||||
for j in range(2):
|
for j in range(2):
|
||||||
plt.text(j, i, cm[i, j], ha="center", va="center", color="black")
|
plt.text(j, i, cm[i, j], ha="center", va="center", color="black")
|
||||||
# plt.show()
|
# 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()
|
plt.close()
|
||||||
|
|
||||||
# ---- Importance des features ----
|
# ---- Importance des features ----
|
||||||
@@ -1727,7 +1890,7 @@ class FrictradeLearning(IStrategy):
|
|||||||
|
|
||||||
plt.title("Importance des features")
|
plt.title("Importance des features")
|
||||||
# plt.show()
|
# 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()
|
plt.close()
|
||||||
|
|
||||||
# ---- Arbre de décision (extrait) ----
|
# ---- Arbre de décision (extrait) ----
|
||||||
@@ -1753,7 +1916,7 @@ class FrictradeLearning(IStrategy):
|
|||||||
plt.title("Courbe ROC")
|
plt.title("Courbe ROC")
|
||||||
plt.legend()
|
plt.legend()
|
||||||
# plt.show()
|
# 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()
|
plt.close()
|
||||||
|
|
||||||
# # ---- Interprétation SHAP (optionnelle) ----
|
# # ---- Interprétation SHAP (optionnelle) ----
|
||||||
@@ -1782,7 +1945,7 @@ class FrictradeLearning(IStrategy):
|
|||||||
#
|
#
|
||||||
# plt.figure(figsize=(12, 4))
|
# plt.figure(figsize=(12, 4))
|
||||||
# shap.summary_plot(shap_values_to_plot, X_to_plot, show=False)
|
# 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()
|
# plt.close()
|
||||||
# except ImportError:
|
# except ImportError:
|
||||||
# print("\n(SHAP non installé — `pip install shap` pour activer l’analyse SHAP.)")
|
# print("\n(SHAP non installé — `pip install shap` pour activer l’analyse SHAP.)")
|
||||||
@@ -1791,7 +1954,7 @@ class FrictradeLearning(IStrategy):
|
|||||||
|
|
||||||
# Trace ou enregistre le graphique
|
# Trace ou enregistre le graphique
|
||||||
self.plot_threshold_analysis(y_valid, y_proba, step=0.05,
|
self.plot_threshold_analysis(y_valid, y_proba, step=0.05,
|
||||||
save_path=f"{output_dir}/threshold_analysis.png")
|
save_path=f"{self.path}/threshold_analysis.png")
|
||||||
|
|
||||||
# y_valid : vraies classes (0 / 1)
|
# y_valid : vraies classes (0 / 1)
|
||||||
# y_proba : probabilités de la classe 1 prédites par ton modèle
|
# y_proba : probabilités de la classe 1 prédites par ton modèle
|
||||||
@@ -1820,7 +1983,7 @@ class FrictradeLearning(IStrategy):
|
|||||||
plt.ylabel("Score")
|
plt.ylabel("Score")
|
||||||
plt.grid(True, alpha=0.3)
|
plt.grid(True, alpha=0.3)
|
||||||
plt.legend()
|
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()
|
# plt.show()
|
||||||
|
|
||||||
print(f"✅ Meilleur F1 : {f1s[best_idx]:.3f} au seuil {seuils[best_idx]:.2f}")
|
print(f"✅ Meilleur F1 : {f1s[best_idx]:.3f} au seuil {seuils[best_idx]:.2f}")
|
||||||
@@ -1837,11 +2000,8 @@ class FrictradeLearning(IStrategy):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# Le graphique généré affichera trois courbes :
|
# Le graphique généré affichera trois courbes :
|
||||||
#
|
|
||||||
# 🔵 Precision — la fiabilité de tes signaux haussiers.
|
# 🔵 Precision — la fiabilité de tes signaux haussiers.
|
||||||
#
|
|
||||||
# 🟢 Recall — la proportion de hausses que ton modèle détecte.
|
# 🟢 Recall — la proportion de hausses que ton modèle détecte.
|
||||||
#
|
|
||||||
# 🟣 F1-score — le compromis optimal entre les deux.
|
# 🟣 F1-score — le compromis optimal entre les deux.
|
||||||
|
|
||||||
thresholds = np.arange(0, 1.01, step)
|
thresholds = np.arange(0, 1.01, step)
|
||||||
@@ -1887,22 +2047,23 @@ class FrictradeLearning(IStrategy):
|
|||||||
and not c.endswith("_state")
|
and not c.endswith("_state")
|
||||||
and not c.endswith("_1d")
|
and not c.endswith("_1d")
|
||||||
# and not c.endswith("_1h")
|
# and not c.endswith("_1h")
|
||||||
# and not c.startswith("open") and not c.startswith("close")
|
and not c.startswith("open") and not c.startswith("close")
|
||||||
# and not c.startswith("low") and not c.startswith("high")
|
and not c.startswith("low") and not c.startswith("high")
|
||||||
# and not c.startswith("haopen") and not c.startswith("haclose")
|
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_lower") and not c.startswith("bb_upper")
|
||||||
# and not c.startswith("bb_middle")
|
# and not c.startswith("bb_middle")
|
||||||
and not c.endswith("_count")
|
and not c.endswith("_count")
|
||||||
and not c.endswith("_class") and not c.endswith("_price")
|
and not c.endswith("_class") and not c.endswith("_price")
|
||||||
and not c.startswith('stop_buying')
|
and not c.startswith('stop_buying')
|
||||||
|
and not c.startswith('target')
|
||||||
and not c.startswith('lvl')
|
and not c.startswith('lvl')
|
||||||
]
|
]
|
||||||
# Étape 3 : remplacer inf et NaN par 0
|
# Étape 3 : remplacer inf et NaN par 0
|
||||||
dataframe[usable_cols] = dataframe[usable_cols].replace([np.inf, -np.inf], 0).fillna(0)
|
dataframe[usable_cols] = dataframe[usable_cols].replace([np.inf, -np.inf], 0).fillna(0)
|
||||||
print("Colonnes utilisables pour le modèle :")
|
print("Colonnes utilisables pour le modèle :")
|
||||||
print(usable_cols)
|
print(usable_cols)
|
||||||
self.model_indicators = usable_cols
|
# self.model_indicators = usable_cols
|
||||||
return self.model_indicators
|
return usable_cols
|
||||||
|
|
||||||
|
|
||||||
def select_uncorrelated_features(self, df, target, top_n=20, corr_threshold=0.7):
|
def select_uncorrelated_features(self, df, target, top_n=20, corr_threshold=0.7):
|
||||||
@@ -2049,3 +2210,71 @@ class FrictradeLearning(IStrategy):
|
|||||||
# self.calculateProbabilite2Index(dataframe, futur_cols=['futur_percent'], indic_1=f"{name}{suffixe}_deriv1", indic_2=f"{name}{suffixe}_deriv2")
|
# self.calculateProbabilite2Index(dataframe, futur_cols=['futur_percent'], indic_1=f"{name}{suffixe}_deriv1", indic_2=f"{name}{suffixe}_deriv2")
|
||||||
|
|
||||||
return dataframe
|
return dataframe
|
||||||
|
|
||||||
|
def calculModelInformative(self, informative):
|
||||||
|
# préparation
|
||||||
|
# print(df)
|
||||||
|
df = informative.copy()
|
||||||
|
X = df[self.listUsableColumns(df)]
|
||||||
|
df['target'] = ((df["sma24"].shift(-13) - df["sma24"]) > 0).astype(int)
|
||||||
|
df['target'] = df['target'].fillna(0).astype(int)
|
||||||
|
y = df['target']
|
||||||
|
|
||||||
|
# train/test
|
||||||
|
X_train, X_test, y_train, y_test = train_test_split(X, y, shuffle=False, test_size=0.2)
|
||||||
|
|
||||||
|
# Pipeline normalisé + Logistic Regresson
|
||||||
|
clf = Pipeline([
|
||||||
|
("scaler", StandardScaler()),
|
||||||
|
("logreg", LogisticRegression(max_iter=5000))
|
||||||
|
])
|
||||||
|
|
||||||
|
# Calibration CV automatique
|
||||||
|
cal = CalibratedClassifierCV(clf, cv=3, method="isotonic")
|
||||||
|
|
||||||
|
# Entraînement
|
||||||
|
cal.fit(X_train, y_train)
|
||||||
|
|
||||||
|
# Probabilités calibrées
|
||||||
|
probas = cal.predict_proba(X_test)[:, 1]
|
||||||
|
# Injection propre des probabilités dans le dataframe original (aux bons index)
|
||||||
|
df.loc[X_test.index, 'ml_prob'] = probas
|
||||||
|
|
||||||
|
print("Brier score:", brier_score_loss(y_test, probas))
|
||||||
|
print("ROC AUC:", roc_auc_score(y_test, probas))
|
||||||
|
|
||||||
|
# joindre probabilités au df (dernières lignes correspondantes)
|
||||||
|
return probas
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|||||||
Reference in New Issue
Block a user