diff --git a/FrictradeLearning.json b/FrictradeLearning.json index 401c89a..40a02be 100644 --- a/FrictradeLearning.json +++ b/FrictradeLearning.json @@ -9,22 +9,27 @@ }, "trailing": { "trailing_stop": false, - "trailing_stop_positive": 0.15, + "trailing_stop_positive": 0.2, "trailing_stop_positive_offset": 1, "trailing_only_offset_is_reached": true }, "max_open_trades": { - "max_open_trades": 80 - }, - "buy": { - "hours_force": 11 + "max_open_trades": 20 }, "protection": { - "allow_decrease_rate": 0.7, - "first_adjust_param": 0.01, - "max_steps": 35 + "allow_decrease_rate": 0.4, + "first_adjust_param": 0.005, + "max_steps": 45 + }, + "buy": { + "hours_force": 44, + "indic_1h_force_buy": "sma5_deriv1_1h" + }, + "sell": { + "offset_max": 18, + "offset_min": 17 } }, "ft_stratparam_v": 1, - "export_time": "2025-12-09 07:22:51.929255+00:00" + "export_time": "2025-12-14 18:44:03.713386+00:00" } \ No newline at end of file diff --git a/FrictradeLearning.py b/FrictradeLearning.py index 0210128..225e3db 100644 --- a/FrictradeLearning.py +++ b/FrictradeLearning.py @@ -5,77 +5,57 @@ # IMPORTANT: INSTALL TA BEFOUR RUN(pip install ta) # freqtrade hyperopt --hyperopt-loss SharpeHyperOptLoss --spaces buy sell roi --strategy Zeus # --- Do not remove these libs --- -from datetime import timedelta, datetime -from freqtrade.persistence import Trade -from freqtrade.strategy import (BooleanParameter, CategoricalParameter, DecimalParameter, stoploss_from_open, - IntParameter, IStrategy, merge_informative_pair, informative, stoploss_from_absolute) -import pandas as pd -import numpy as np -import os -import json -import csv -from pandas import DataFrame -from typing import Optional, Union, Tuple -import math +import inspect import logging -from pathlib import Path +import os -# -------------------------------- +from datetime import datetime +from datetime import timezone +from typing import Optional +import freqtrade.vendor.qtpylib.indicators as qtpylib +# Machine Learning +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 # Add your lib to import here test git import ta import talib.abstract as talib -import freqtrade.vendor.qtpylib.indicators as qtpylib -from datetime import timezone, timedelta -import mpmath as mp - -# Machine Learning -from sklearn.ensemble import RandomForestClassifier,RandomForestRegressor -from sklearn.model_selection import train_test_split -from sklearn.metrics import r2_score, mean_absolute_error, mean_squared_error -from sklearn.metrics import accuracy_score -import joblib -import matplotlib.pyplot as plt +from freqtrade.persistence import Trade +from freqtrade.strategy import (CategoricalParameter, DecimalParameter, IntParameter, IStrategy, merge_informative_pair) +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_auc_score, roc_curve, - precision_score, recall_score, precision_recall_curve, - f1_score + precision_score, recall_score ) -from sklearn.tree import export_text -import inspect -from sklearn.feature_selection import mutual_info_classif -from sklearn.inspection import permutation_importance -from lightgbm import LGBMClassifier -from sklearn.calibration import CalibratedClassifierCV -from sklearn.feature_selection import SelectFromModel -from tabulate import tabulate -from sklearn.model_selection import GridSearchCV -from sklearn.feature_selection import VarianceThreshold -import seaborn as sns -from xgboost import XGBClassifier -import optuna -from optuna.visualization import plot_optimization_history -from optuna.visualization import plot_slice -from optuna.visualization import plot_param_importances -from optuna.visualization import plot_parallel_coordinate -import shap -from sklearn.inspection import PartialDependenceDisplay - -from sklearn.model_selection import train_test_split 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 -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__) @@ -91,7 +71,7 @@ RESET = "\033[0m" class FrictradeLearning(IStrategy): - startup_candle_count = 180 + startup_candle_count = 200 train_model = None model_indicators = [] DEFAULT_PARAMS = { @@ -106,13 +86,21 @@ class FrictradeLearning(IStrategy): "stoploss": -0.10, "minimal_roi": {"0": 0.10} } + indicators = {'sma24_deriv1', 'sma60_deriv1', 'sma5_deriv1_1h', 'sma12_deriv1_1h', 'sma24_deriv1_1h', + 'sma60_deriv1_1h'} + indic_1h_force_buy = CategoricalParameter(indicators, default="sma60_deriv1", space='buy') - allow_decrease_rate = DecimalParameter(0.1, 0.8, decimals=1, default=0.4, space='protection') - first_adjust_param = DecimalParameter(0.001, 0.01, decimals=3, default=0.003, space='protection') + allow_decrease_rate = DecimalParameter(0.1, 0.8, decimals=1, default=0.4, space='protection', optimize=False, + load=False) + first_adjust_param = DecimalParameter(0.001, 0.01, decimals=3, default=0.005, space='protection', optimize=False, + load=False) max_steps = IntParameter(10, 50, default=40, space='protection', optimize=True, load=True) hours_force = IntParameter(1, 48, default=24, space='buy', optimize=True, load=True) + offset_min = IntParameter(1, 48, default=24, space='sell', optimize=True, load=True) + offset_max = IntParameter(1, 48, default=24, space='sell', optimize=True, load=True) + # ROI table: minimal_roi = { "0": 10 @@ -157,11 +145,13 @@ class FrictradeLearning(IStrategy): 'stop': False, 'max_profit': 0, 'first_amount': 0, + 'first_price': 0, 'total_amount': 0, 'has_gain': 0, 'force_sell': False, 'force_buy': False, 'last_ath': 0, + 'mises': {}, 'dca_thresholds': {} } for pair in ["BTC/USDC", "ETH/USDC", "DOGE/USDC", "XRP/USDC", "SOL/USDC", @@ -178,11 +168,30 @@ class FrictradeLearning(IStrategy): {"date": "2021-11-10", "price_usd": 68742.00, "note": "record novembre 2021 (institutional demand)"}, {"date": "2024-03-05", "price_usd": 69000.00, "note": "nouveau pic début 2024 (source presse, valeur indicative)"}, + {"date": "2024-03-14", "price_usd": 73816.00, + "note": "nouveau pic début 2024 (source presse, valeur indicative)"}, + {"date": "2024-11-12", "price_usd": 90000.00, "note": ""}, + {"date": "2024-12-17", "price_usd": 108363.00, "note": ""}, {"date": "2025-07-11", "price_usd": 118755.00, "note": "pic juillet 2025 (valeur rapportée par la presse)"}, + {"date": "2025-08-13", "price_usd": 123748.00, "note": ""}, {"date": "2025-10-06", "price_usd": 126198.07, "note": "pic oct. 2025 (source agrégée, à vérifier selon l'exchange)"} ] + def dynamic_trailing_offset(self, price, ath, nb_entries, max_dca=5): + dd_ath = (ath - price) / ath + dd_ath = max(0.0, min(dd_ath, 0.5)) + + dca_risk = min(nb_entries / max_dca, 1.0) + + breathing_score = 0.7 * dd_ath + 0.3 * (1 - dca_risk) + breathing_score = min(max(breathing_score, 0.0), 1.0) + + OFFSET_MIN = self.offset_min.value + OFFSET_MAX = self.offset_min.value + self.offset_max.value + + return OFFSET_MIN + breathing_score * (OFFSET_MAX - OFFSET_MIN) + def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float, time_in_force: str, current_time: datetime, entry_tag: Optional[str], **kwargs) -> bool: @@ -195,9 +204,9 @@ class FrictradeLearning(IStrategy): last_candle_2 = dataframe.iloc[-2].squeeze() last_candle_3 = dataframe.iloc[-3].squeeze() - condition = True #(last_candle[f"{indic_5m}_deriv1"] >= indic_deriv1_5m) and (last_candle[f"{indic_5m}_deriv2"] >= indic_deriv2_5m) + condition = True # (last_candle[f"{indic_5m}_deriv1"] >= indic_deriv1_5m) and (last_candle[f"{indic_5m}_deriv2"] >= indic_deriv2_5m) - allow_to_buy = True #(condition and not self.pairs[pair]['stop']) | (entry_tag == 'force_entry') + allow_to_buy = True # (condition and not self.pairs[pair]['stop']) | (entry_tag == 'force_entry') if allow_to_buy: self.trades = list() @@ -235,43 +244,48 @@ class FrictradeLearning(IStrategy): return allow_to_buy + def progressive_parts(self, total, n, first): + # print('In part') + # conditions impossibles → on évite le solveur + if total <= 0 or first <= 0 or n <= 1: + return [0] * n + + f = lambda r: first * (r ** n - 1) / (r - 1) - total + + try: + r = mp.findroot(f, 1.2) # 1.2 = plus stable que 1.05 + except Exception: + # fallback en cas d'échec + return [first] * n + + parts = [round(first * (r ** k), 4) for k in range(n)] + return parts + def calculateStepsDcaThresholds(self, last_candle, pair): # def split_ratio_one_third(n, p): # a = n / (2 * p) # première valeur # d = n / (p * (p - 1)) # incrément # return [round(a + i * d, 3) for i in range(p)] - def progressive_parts(total, n, first): - print('In part') - # conditions impossibles → on évite le solveur - if total <= 0 or first <= 0 or n <= 1: - return [0] * n - - f = lambda r: first * (r ** n - 1) / (r - 1) - total - - try: - r = mp.findroot(f, 1.2) # 1.2 = plus stable que 1.05 - except Exception: - # fallback en cas d'échec - return [first] * n - - parts = [round(first * (r ** k), 4) for k in range(n)] - return parts # r, parts = progressive_parts(0.4, 40, 0.004) # print("r =", r) # print(parts) - if self.pairs[pair]['last_ath'] == 0 : - ath = max(last_candle['mid'], self.get_last_ath_before_candle(last_candle)) + val = self.pairs[pair]['first_price'] if self.pairs[pair]['first_price'] > 0 else last_candle['mid'] + + if self.pairs[pair]['last_ath'] == 0: + ath = max(val, self.get_last_ath_before_candle(last_candle['date'])) self.pairs[pair]['last_ath'] = ath - steps = self.approx_value(last_candle['mid'], self.pairs[pair]['last_ath']) - self.pairs[pair]['dca_thresholds'] = progressive_parts( - (last_candle['mid'] - (self.pairs[pair]['last_ath'] * (1 - self.allow_decrease_rate.value))) / last_candle['mid'], + ath = self.pairs[pair]['last_ath'] + + steps = self.calculateNumberOfSteps(val, ath, max_steps=self.max_steps.value) + self.pairs[pair]['dca_thresholds'] = self.progressive_parts( + (val - (ath * (1 - self.allow_decrease_rate.value))) / val, steps, self.first_adjust_param.value) - print(f"val={last_candle['mid']} lim={self.pairs[pair]['last_ath'] * (1 - self.allow_decrease_rate.value)}" - f"steps={steps} " - f"pct={(round(last_candle['mid'] - (self.pairs[pair]['last_ath'] * (1 - self.allow_decrease_rate.value))) / last_candle['mid'], 4)}") + print(f"val={val} lim={self.pairs[pair]['last_ath'] * (1 - self.allow_decrease_rate.value)}" + f" steps={steps}" + f" pct={round((val - (self.pairs[pair]['last_ath'] * (1 - self.allow_decrease_rate.value))) / val, 4)}") print(self.pairs[pair]['dca_thresholds']) def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount: float, rate: float, @@ -283,9 +297,10 @@ class FrictradeLearning(IStrategy): last_candle = dataframe.iloc[-1].squeeze() minutes = int(round((current_time - trade.open_date_utc).seconds / 60, 0)) - profit =trade.calc_profit(rate) + profit = trade.calc_profit(rate) force = self.pairs[pair]['force_sell'] - allow_to_sell = minutes > 30 and (last_candle['hapercent'] < 0 ) or force or (exit_reason == 'force_exit') or (exit_reason == 'stop_loss') + # and (last_candle['hapercent'] < 0 ) + allow_to_sell = True # (last_candle['hapercent'] < 0 ) or force or (exit_reason == 'force_exit') or (exit_reason == 'stop_loss') if allow_to_sell: self.trades = list() @@ -306,6 +321,7 @@ class FrictradeLearning(IStrategy): dispo=dispo, profit=round(profit, 2) ) + self.pairs[pair]['first_amount'] = 0 self.pairs[pair]['max_profit'] = 0 self.pairs[pair]['force_sell'] = False self.pairs[pair]['has_gain'] = 0 @@ -316,8 +332,9 @@ class FrictradeLearning(IStrategy): self.pairs[pair]['last_buy'] = 0 self.pairs[pair]['last_date'] = current_time self.pairs[pair]['current_trade'] = None - # else: - # self.printLog(f"{current_time} SELL triggered for {pair} ({exit_reason} profit={profit} minutes={minutes} percent={last_candle['hapercent']}) but condition blocked") + else: + self.printLog( + f"{current_time} SELL triggered for {pair} ({exit_reason} profit={profit} minutes={minutes} percent={last_candle['hapercent']}) but condition blocked") return (allow_to_sell) | (exit_reason == 'force_exit') | (exit_reason == 'stop_loss') # def custom_exit(self, pair: str, trade: Trade, current_time, current_rate, current_profit, **kwargs): @@ -387,6 +404,7 @@ class FrictradeLearning(IStrategy): def getLastLost(self, last_candle, pair): last_lost = round((last_candle['close'] - self.pairs[pair]['max_touch']) / self.pairs[pair]['max_touch'], 3) return last_lost + def getPctFirstBuy(self, pair, last_candle): return round((last_candle['close'] - self.pairs[pair]['first_buy']) / self.pairs[pair]['first_buy'], 3) @@ -400,7 +418,8 @@ class FrictradeLearning(IStrategy): lim = 0.005 pct = 0.001 pct_to_max = lim + pct * self.pairs[pair]['count_of_buys'] - expected_profit = lim * self.pairs[pair]['total_amount'] # min(3 * lim, max(lim, pct_to_max)) # 0.004 + 0.002 * self.pairs[pair]['count_of_buys'] #min(0.01, first_max) + expected_profit = lim * self.pairs[pair][ + 'total_amount'] # min(3 * lim, max(lim, pct_to_max)) # 0.004 + 0.002 * self.pairs[pair]['count_of_buys'] #min(0.01, first_max) self.pairs[pair]['expected_profit'] = expected_profit @@ -415,7 +434,8 @@ class FrictradeLearning(IStrategy): self.printLog( 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"{'rsi':>6}|{'rsi_1h':>6}|{'rsi_1d':>6}|{'mlprob':>6}" #|Distmax|s201d|s5_1d|s5_2d|s51h|s52h|smt1h|smt2h|tdc1d|tdc1h" + f"{'rsi':>6}|{'rsi_1h':>6}|{'rsi_1d':>6}|{'cf_1h':>6}|{'cf_1d':>6}" + # |Distmax|s201d|s5_1d|s5_2d|s51h|s52h|smt1h|smt2h|tdc1d|tdc1h" ) self.printLineLog() df = pd.DataFrame.from_dict(self.pairs, orient='index') @@ -467,7 +487,7 @@ class FrictradeLearning(IStrategy): 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['rtp_1h'] * 100, 0) or '-' :>6}|{round(last_candle['rtp_1d'] * 100, 0) or '-' :>6}|" - f"{round(last_candle['ml_prob'], 1) or '-':>6}|" + # f"{round(last_candle['confidence_index_1d'], 3) or '-':>6}|{round(last_candle['confidence_index_1h'], 3) or '-':>6}|" ) def printLineLog(self): @@ -490,7 +510,7 @@ class FrictradeLearning(IStrategy): # Add all ta features pair = metadata['pair'] short_pair = self.getShortName(pair) - self.path = f"user_data/strategies/plots/{short_pair}/" # + ("valide/" if not self.dp.runmode.value in ('backtest') else '') + self.path = f"user_data/strategies/plots/{short_pair}/" # + ("valide/" if not self.dp.runmode.value in ('backtest') else '') heikinashi = qtpylib.heikinashi(dataframe) dataframe['haopen'] = heikinashi['open'] @@ -498,7 +518,8 @@ class FrictradeLearning(IStrategy): dataframe['hapercent'] = (dataframe['haclose'] - dataframe['haopen']) / dataframe['haclose'] dataframe['mid'] = dataframe['open'] + (dataframe['close'] - dataframe['open']) / 2 - 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['sma12'] = dataframe['mid'].ewm(span=12, adjust=False).mean() @@ -506,10 +527,12 @@ class FrictradeLearning(IStrategy): 'sma12'].shift(1) 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) dataframe['sma60'] = dataframe['mid'].ewm(span=60, adjust=False).mean() - dataframe['sma60_deriv1'] = 1000 * (dataframe['sma60'] - dataframe['sma60'].shift(1)) / dataframe['sma60'].shift(1) + dataframe['sma60_deriv1'] = 1000 * (dataframe['sma60'] - dataframe['sma60'].shift(1)) / dataframe[ + 'sma60'].shift(1) # dataframe[f"sma5_inv"] = (dataframe[f"sma5"].shift(2) >= dataframe[f"sma5"].shift(1)) \ # & (dataframe[f"sma5"].shift(1) <= dataframe[f"sma5"]) @@ -546,7 +569,7 @@ class FrictradeLearning(IStrategy): dataframe['max5'] = talib.MAX(dataframe['mid'], timeperiod=5) dataframe['min180'] = talib.MIN(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) # ################### INFORMATIVE 1h @@ -564,16 +587,30 @@ class FrictradeLearning(IStrategy): informative['macdhist'] = macdhist informative['rsi'] = talib.RSI(informative['mid'], timeperiod=14) + + informative['sma5'] = informative['mid'].ewm(span=5, adjust=False).mean() + informative['sma5_deriv1'] = 1000 * (informative['sma5'] - informative['sma5'].shift(1)) / informative[ + 'sma5'].shift(1) + + informative['sma12'] = informative['mid'].ewm(span=12, adjust=False).mean() + informative['sma12_deriv1'] = 1000 * (informative['sma12'] - informative['sma12'].shift(1)) / informative[ + 'sma12'].shift(1) + 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['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['sma60_deriv1'] = 1000 * (informative['sma60'] - informative['sma60'].shift(1)) / informative[ + 'sma60'].shift(1) + informative['rsi'] = talib.RSI(informative['mid'], timeperiod=14) self.calculeDerivees(informative, 'rsi', ema_period=12) # informative = self.rsi_trend_probability(informative) - probas = self.calculModelInformative(informative) + # probas = self.calculModelInformative(informative) + + # self.calculateConfiance(informative) # informative = self.populate1hIndicators(df=informative, metadata=metadata) # informative = self.calculateRegression(informative, 'mid', lookback=15) @@ -585,6 +622,8 @@ class FrictradeLearning(IStrategy): informative['rsi'] = talib.RSI(informative['mid'], timeperiod=5) # informative = self.rsi_trend_probability(informative) # informative = self.calculateRegression(informative, 'mid', lookback=15) + # self.calculateConfiance(informative) + dataframe = merge_informative_pair(dataframe, informative, '1m', '1d', ffill=True) dataframe['last_price'] = dataframe['close'] @@ -599,8 +638,8 @@ class FrictradeLearning(IStrategy): filled_buys = trade.select_filled_orders('buy') count = 0 amount = 0 - min_price = 111111111111110; - max_price = 0; + min_price = 111111111111110 + max_price = 0 for buy in filled_buys: if count == 0: min_price = min(min_price, buy.price) @@ -616,7 +655,7 @@ class FrictradeLearning(IStrategy): self.pairs[pair]['last_buy'] = buy.price count = count + 1 amount += buy.price * buy.filled - count_buys = count + self.pairs[pair]['count_of_buys'] = count self.pairs[pair]['total_amount'] = amount dataframe['absolute_min'] = dataframe['mid'].rolling(1440, min_periods=1).min() @@ -633,8 +672,8 @@ class FrictradeLearning(IStrategy): dataframe['bb_middleband'] = bollinger['mid'] dataframe['bb_upperband'] = bollinger['upper'] dataframe["bb_percent"] = ( - (dataframe["close"] - dataframe["bb_lowerband"]) / - (dataframe["bb_upperband"] - dataframe["bb_lowerband"]) + (dataframe["close"] - dataframe["bb_lowerband"]) / + (dataframe["bb_upperband"] - dataframe["bb_lowerband"]) ) dataframe["bb_width"] = (dataframe["bb_upperband"] - dataframe["bb_lowerband"]) / dataframe["sma24"] @@ -845,11 +884,55 @@ class FrictradeLearning(IStrategy): # d = n / (p * (p - 1)) # incrément # return [round(a + i * d, 3) for i in range(p)] # - # for val in range(90000, 130000, 10000): - # steps = self.approx_value(val, 126000) - # print(f"val={val} steps={steps} pct={(val - (126000 * (1 - self.allow_decrease_rate.value))) / val}") - # dca = split_ratio_one_third((val - (126000 * (1 - self.allow_decrease_rate.value))) / 126000, steps) - # print(dca) + allow_decrease_rate = 0.3 + # for val in range(70000, 140000, 10000): + # ath = 126000 + # + # steps = self.calculateNumberOfSteps(val, ath, max_steps=40) + # self.printLog(f"allow_decrease_rate={self.allow_decrease_rate.value} val={val} steps={steps} pct={round((val - (ath * (1 - allow_decrease_rate))) / val, 4)}") + # # dca = split_ratio_one_third((val - (ath * (1 - self.allow_decrease_rate.value))) / ath, steps) + # # self.printLog(dca) + # dca_thresholds = self.progressive_parts( + # (val - (ath * (1 - self.allow_decrease_rate.value))) / val, + # steps, self.first_adjust_param.value) + # print(f"val={val} lim={ath * (1 - self.allow_decrease_rate.value)}" + # f"steps={steps} " + # f"pct={(round(val - (ath * (1 - self.allow_decrease_rate.value))) / val, 4)}") + # print(dca_thresholds) + ath = 126000 + + last_candle = dataframe.iloc[-1].squeeze() + val = last_candle['first_price'] + # steps = self.calculateNumberOfSteps(val, ath, max_steps=40) + # self.printLog( + # f"allow_decrease_rate={self.allow_decrease_rate.value} val={val} steps={steps} pct={round((val - (ath * (1 - allow_decrease_rate))) / val, 4)}") + # dca_thresholds = self.progressive_parts((val - (ath * (1 - self.allow_decrease_rate.value))) / val, steps, self.first_adjust_param.value) + # print(f"val={val} lim={ath * (1 - self.allow_decrease_rate.value)}" + # f"steps={steps} " + # f"pct={(round(val - (ath * (1 - self.allow_decrease_rate.value))) / val, 4)}") + # print(dca_thresholds) + + + if self.dp: + if self.dp.runmode.value in ('live', 'dry_run'): + full, mises, steps = self.calculateMises(last_candle, pair) + # stake = min(self.wallets.get_available_stake_amount(), self.adjust_stake_amount(pair, last_candle)) + if val and len(self.pairs[pair]['dca_thresholds']) > 0: + print(self.pairs[pair]['dca_thresholds']) + count = 0 + pct = 0 + dataframe = dataframe.copy() + for dca in self.pairs[pair]['dca_thresholds']: + stake = mises[count] + pct += dca + offset = self.dynamic_trailing_offset(price=val, ath=ath, nb_entries=count) + + if (count == self.pairs[pair]['count_of_buys']): + print(f"next_buy={val * (1 - pct)} count={count} pct={round(pct, 4)}") + dataframe[f"next_buy"] = val * (1 - pct) + count += 1 + print( + f"stake={stake} count={count} pct={round(pct, 4)} offset={offset} next_buy={val * (1 - pct)}") return dataframe @@ -910,9 +993,9 @@ class FrictradeLearning(IStrategy): (dataframe["ml_prob"].shift(1) < dataframe["ml_prob"]) & (dataframe['sma24_deriv1'] > 0) & (dataframe['sma12_deriv1'] > 0) - & (dataframe['open'] < dataframe['max180'] * 0.997), - # & (dataframe['min180'].shift(3) == dataframe['min180']), - ['enter_long', 'enter_tag'] + # & (dataframe['open'] < dataframe['max180'] * 0.997) + # & (dataframe['min180'].shift(3) == dataframe['min180']) + , ['enter_long', 'enter_tag'] ] = (1, f"future") dataframe['test'] = np.where(dataframe['enter_long'] == 1, dataframe['close'] * 1.003, np.nan) @@ -1029,50 +1112,40 @@ class FrictradeLearning(IStrategy): # # return adjusted_stake_amount - def approx_value(self, x, X_max): - X_min = X_max * (1 - self.allow_decrease_rate.value) # 126198 * 0.4 = 75718,8 + def calculateNumberOfSteps(self, current, ath, max_steps=0): + if (max_steps == 0): + max_steps = self.max_steps.value + + X_min = ath * (1 - self.allow_decrease_rate.value) # 126198 * 0.4 = 75718,8 Y_min = 1 - Y_max = self.max_steps.value - a = (Y_max - Y_min) / (X_max - X_min) # 39 ÷ (126198 − 126198×0,6) = 0,000772595 - b = Y_min - a * X_min # 1 − (0,000772595 × 75718,8) = −38 - y = a * x + b # 0,000772595 * 115000 - 38 + Y_max = max_steps + a = (Y_max - Y_min) / (ath - X_min) # 39 ÷ (126198 − 126198×0,6) = 0,000772595 + b = Y_min - a * X_min # 1 − (0,000772595 × 75718,8) = −38 + y = a * current + b # 0,000772595 * 115000 - 38 return max(round(y), 1) # évite les valeurs négatives def adjust_stake_amount(self, pair: str, last_candle: DataFrame): if self.pairs[pair]['first_amount'] > 0: - return self.pairs[pair]['first_amount'] + mises = self.pairs[pair]['mises'] + count = self.pairs[pair]['count_of_buys'] - self.pairs[pair]['has_gain'] + return mises[count] if count < len(mises) else self.pairs[pair]['first_amount'] - ath = max(self.pairs[pair]['last_max'], self.get_last_ath_before_candle(last_candle)) + full, mises, steps = self.calculateMises(last_candle, pair) + + base_stake = mises[self.pairs[pair]['count_of_buys']] if self.pairs[pair]['count_of_buys'] < len( + mises) else full / (steps * 2) + return base_stake + + def calculateMises(self, last_candle, pair): + ath = max(self.pairs[pair]['last_max'], self.get_last_ath_before_candle(last_candle['date'])) self.pairs[pair]['last_ath'] = ath - - ath_dist = 100 * (ath - last_candle["mid"]) / ath - - - # ath_dist - # 0 ==> 1 - # 20 ==> 1.5 - # 40 ==> 2 - # 50 * (1 + (ath_dist / 40)) - full = self.wallets.get_total_stake_amount() - steps = self.approx_value(last_candle['mid'], ath) - base_stake = full / steps - - # base_stake = stake * (1 + (ath_dist / 40)) - - # Calcule max/min 180 - low180 = last_candle["min180"] - high180 = last_candle["max180"] - - mult = 1 - ((last_candle["mid"] - low180) / (high180 - low180)) - - print(f"low={low180} mid={last_candle['mid']} high={high180} mult={mult} ath={ath} ath_dist={round(ath_dist, 2)}" ) - # base_size = montant de base que tu veux utiliser (ex: stake_amount ou autre) - base_size = base_stake # exemple fraction du portefeuille; adapte selon ton code - # new stake proportionnel à mult - new_stake = base_size #* mult - return new_stake + steps = self.calculateNumberOfSteps(last_candle['mid'], ath, max_steps=self.max_steps.value) + mises = self.progressive_parts(full, steps, full / (steps * 2)) + print(f"ath={ath} full={full} steps={steps} mises={mises} ") + self.pairs[pair]['mises'] = mises + return full, mises, steps def adjust_trade_position(self, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, min_stake: float, @@ -1083,6 +1156,11 @@ class FrictradeLearning(IStrategy): return None dataframe, _ = self.dp.get_analyzed_dataframe(trade.pair, self.timeframe) + + if (len(dataframe) < 1): + # self.printLog("skip dataframe") + return None + last_candle = dataframe.iloc[-1].squeeze() # before_last_candle = dataframe.iloc[-2].squeeze() # prépare les données @@ -1099,7 +1177,7 @@ class FrictradeLearning(IStrategy): # open_date = trade.open_date.astimezone(timezone.utc) # days_since_open = (current_time_utc - open_date).days pair = trade.pair - profit = trade.calc_profit(current_rate) #round(current_profit * trade.stake_amount, 1) + profit = trade.calc_profit(current_rate) # round(current_profit * trade.stake_amount, 1) # last_lost = self.getLastLost(last_candle, pair) pct_first = 0 @@ -1109,32 +1187,32 @@ class FrictradeLearning(IStrategy): # if self.pairs[pair]['first_buy']: # pct_first = self.getPctFirstBuy(pair, last_candle) - if profit > - self.pairs[pair]['first_amount'] \ - and self.wallets.get_available_stake_amount() < self.pairs[pair]['first_amount'] \ - and last_candle['sma24_deriv1_1h'] < 0: - stake_amount = trade.stake_amount - self.pairs[pair]['previous_profit'] = profit - trade_type = "Sell " + (last_candle['enter_tag'] if last_candle['enter_long'] == 1 else '') - self.pairs[trade.pair]['count_of_buys'] += 1 - self.pairs[pair]['total_amount'] = stake_amount - self.log_trade( - last_candle=last_candle, - date=current_time, - action="🟧 Sell +", - dispo=dispo, - pair=trade.pair, - rate=current_rate, - trade_type=trade_type, - profit=round(profit, 1), - buys=trade.nr_of_successful_entries + 1, - stake=round(stake_amount, 2) - ) - - self.pairs[trade.pair]['last_buy'] = current_rate - self.pairs[trade.pair]['max_touch'] = last_candle['close'] - self.pairs[trade.pair]['last_candle'] = last_candle - - return -stake_amount + # if profit > - self.pairs[pair]['first_amount'] \ + # and self.wallets.get_available_stake_amount() < self.pairs[pair]['first_amount'] \ + # and last_candle['sma24_deriv1_1h'] < 0: + # stake_amount = trade.stake_amount + # self.pairs[pair]['previous_profit'] = profit + # trade_type = "Sell " + (last_candle['enter_tag'] if last_candle['enter_long'] == 1 else '') + # self.pairs[trade.pair]['count_of_buys'] += 1 + # self.pairs[pair]['total_amount'] = stake_amount + # self.log_trade( + # last_candle=last_candle, + # date=current_time, + # action="🟥 Stoploss", + # dispo=dispo, + # pair=trade.pair, + # rate=current_rate, + # trade_type=trade_type, + # profit=round(profit, 1), + # buys=trade.nr_of_successful_entries + 1, + # stake=round(stake_amount, 2) + # ) + # + # self.pairs[trade.pair]['last_buy'] = current_rate + # self.pairs[trade.pair]['max_touch'] = last_candle['close'] + # self.pairs[trade.pair]['last_candle'] = last_candle + # + # return -stake_amount if (self.wallets.get_available_stake_amount() < 10): # or trade.stake_amount >= max_stake: return 0 @@ -1145,7 +1223,7 @@ class FrictradeLearning(IStrategy): return None # Dernier prix d'achat réel (pas le prix moyen) - last_fill_price = self.pairs[trade.pair]['last_buy'] #trade.open_rate # remplacé juste après ↓ + last_fill_price = self.pairs[trade.pair]['last_buy'] # trade.open_rate # remplacé juste après ↓ # if len(trade.orders) > 0: # # On cherche le dernier BUY exécuté @@ -1155,7 +1233,7 @@ class FrictradeLearning(IStrategy): # baisse relative # if minutes % 60 == 0: - # 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['date'])) # self.pairs[pair]['last_ath'] = ath # else: # ath = self.pairs[pair]['last_ath'] @@ -1166,9 +1244,10 @@ class FrictradeLearning(IStrategy): if len(self.pairs[pair]['dca_thresholds']) == 0: self.calculateStepsDcaThresholds(last_candle, pair) - dca_threshold = self.pairs[pair]['dca_thresholds'][min(count_of_buys - 1, len(self.pairs[pair]['dca_thresholds']) - 1)] + dca_threshold = self.pairs[pair]['dca_thresholds'][ + min(count_of_buys - 1, len(self.pairs[pair]['dca_thresholds']) - 1)] - # print(f"{count_of_buys} {ath * (1 - self.allow_decrease_rate.value)} {round(last_candle['mid'], 2)} {round((last_candle['mid'] - (ath * self.allow_decrease_rate.value)) / last_candle['mid'], 2)} {steps} {round(dca_threshold, 4)}") + # self.printLog(f"{count_of_buys} {ath * (1 - self.allow_decrease_rate.value)} {round(last_candle['mid'], 2)} {round((last_candle['mid'] - (ath * self.allow_decrease_rate.value)) / last_candle['mid'], 2)} {steps} {round(dca_threshold, 4)}") decline = (last_fill_price - current_rate) / last_fill_price increase = - decline @@ -1178,10 +1257,9 @@ class FrictradeLearning(IStrategy): # stake_amount = last_amount * current_rate * 0.5 # return stake_amount - ########################### ALGO ATH # # --- 1. Calcul ATH local de la paire --- - # 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['date'])) # # # --- 2. Distance ATH - current --- # dd = (current_rate - ath) / ath * 100 # dd <= 0 @@ -1208,7 +1286,7 @@ class FrictradeLearning(IStrategy): # return None # FIN ########################## ALGO ATH - force = hours > self.hours_force.value and last_candle['sma60_deriv1_1h'] > 0 + force = False #hours > self.hours_force.value and last_candle[self.indic_1h_force_buy.value] > 0 condition = last_candle['percent'] > 0 and last_candle['sma24_deriv1'] > 0 \ and last_candle['close'] < self.pairs[pair]['first_buy'] @@ -1219,11 +1297,12 @@ class FrictradeLearning(IStrategy): self.pairs[pair]['previous_profit'] = profit return None - stake_amount = min(self.wallets.get_available_stake_amount(), self.adjust_stake_amount(pair, last_candle)) + stake_amount = min(self.wallets.get_available_stake_amount(), + self.adjust_stake_amount(pair, last_candle)) - if force: - stake_amount = stake_amount / 4 - # print(f"profit={profit} previous={self.pairs[pair]['previous_profit']} count_of_buys={trade.nr_of_successful_entries}") + # if force: + # stake_amount = stake_amount / 2 + # self.printLog(f"profit={profit} previous={self.pairs[pair]['previous_profit']} count_of_buys={trade.nr_of_successful_entries}") if stake_amount > 0: self.pairs[pair]['previous_profit'] = profit trade_type = "Loss " + (last_candle['enter_tag'] if last_candle['enter_long'] == 1 else '') @@ -1260,11 +1339,14 @@ class FrictradeLearning(IStrategy): self.printLog(exception) return None - if current_profit > dca_threshold and (increase >= dca_threshold and self.wallets.get_available_stake_amount() > 0)\ + increase_dca_threshold = 0.003 + if current_profit > increase_dca_threshold \ + and (increase >= increase_dca_threshold and self.wallets.get_available_stake_amount() > 0) \ and last_candle['rsi'] < 75: try: self.pairs[pair]['previous_profit'] = profit - stake_amount = max(20, min(self.wallets.get_available_stake_amount(), self.adjust_stake_amount(pair, last_candle))) + stake_amount = max(10, min(self.wallets.get_available_stake_amount(), + self.adjust_stake_amount(pair, last_candle))) if stake_amount > 0: self.pairs[pair]['has_gain'] += 1 @@ -1278,7 +1360,7 @@ class FrictradeLearning(IStrategy): dispo=dispo, pair=trade.pair, rate=current_rate, - trade_type='Gain ' + str(round(dca_threshold, 4)), + trade_type='Gain ' + str(round(increase, 4)), profit=round(profit, 1), buys=trade.nr_of_successful_entries + 1, stake=round(stake_amount, 2) @@ -1304,7 +1386,7 @@ class FrictradeLearning(IStrategy): # before_last_candle_12 = dataframe.iloc[-13].squeeze() # # expected_profit = self.expectedProfit(pair, last_candle) - # # print(f"current_time={current_time} current_profit={current_profit} expected_profit={expected_profit}") + # # self.printLog(f"current_time={current_time} current_profit={current_profit} expected_profit={expected_profit}") # # # ----- 1) Charger les variables de trailing pour ce trade ----- # max_price = self.pairs[pair]['max_touch'] @@ -1315,7 +1397,7 @@ class FrictradeLearning(IStrategy): count_of_buys = trade.nr_of_successful_entries - profit = trade.calc_profit(current_rate) #round(current_profit * trade.stake_amount, 1) + profit = trade.calc_profit(current_rate) # round(current_profit * trade.stake_amount, 1) if current_profit > 0: self.pairs[pair]['max_profit'] = max(self.pairs[pair]['max_profit'], profit) @@ -1325,8 +1407,7 @@ class FrictradeLearning(IStrategy): max_profit = self.pairs[pair]['max_profit'] # if current_profit > 0: - # print(f"profit={profit} max_profit={max_profit} current_profit={current_profit}") - + # self.printLog(f"profit={profit} max_profit={max_profit} current_profit={current_profit}") # baisse = 0 # if profit > 0: @@ -1351,6 +1432,10 @@ class FrictradeLearning(IStrategy): current_trailing_only_offset_is_reached = self.trailing_only_offset_is_reached current_trailing_stop_positive_offset = self.trailing_stop_positive_offset + current_trailing_stop_positive_offset = self.dynamic_trailing_offset(price=current_rate, + ath=self.pairs[pair]['last_ath'], + nb_entries=count_of_buys) + # max_ = last_candle['max180'] # min_ = last_candle['min180'] # mid = last_candle['mid'] @@ -1393,8 +1478,8 @@ class FrictradeLearning(IStrategy): if last_candle['rsi'] > 90: return f"rsi_{count_of_buys}_{self.pairs[pair]['has_gain']}" - if last_candle['sma12'] > last_candle['sma24']: # and last_candle['rsi'] < 85: - return None + # if last_candle['sma12'] > last_candle['sma24']: # and last_candle['rsi'] < 85: + # return None # if last_candle['sma24_deriv1'] > 0 : #and minutes < 180 and baisse < 30: # and last_candle['sma5_deriv1'] > -0.15: # if (minutes < 180): @@ -1402,27 +1487,28 @@ class FrictradeLearning(IStrategy): # if (minutes > 1440 and last_candle['sma60_deriv1'] > 0) : # return None - # ----- 4) OFFSET : faut-il attendre de dépasser trailing_stop_positive_offset ? ----- if current_trailing_only_offset_is_reached and max_profit > current_trailing_stop_positive_offset: # Max profit pas atteint ET perte < 2 * current_trailing_stop_positive - if profit > max_profit * (1 - current_trailing_stop_positive): #2 * current_trailing_stop_positive: - print(f"{current_time} trailing non atteint trailing_stop={round(trailing_stop,4)} profit={round(profit, 4)} max={round(max_profit, 4)} " - f"{min(2, max_profit * (1 - current_trailing_stop_positive))}") + if profit > max_profit * (1 - current_trailing_stop_positive): # 2 * current_trailing_stop_positive: + print( + f"{current_time} trailing non atteint trailing_stop={round(trailing_stop, 4)} profit={round(profit, 4)} " + f"max={round(max_profit, 4)} offset={current_trailing_stop_positive_offset}") return None # ne pas activer le trailing encore else: - print(f"{current_time} trailing atteint trailing_stop={round(trailing_stop,4)} profit={round(profit, 4)} max={round(max_profit, 4)} " - f"{min(2,max_profit * (1 - current_trailing_stop_positive))}") + print( + f"{current_time} trailing atteint trailing_stop={round(trailing_stop, 4)} profit={round(profit, 4)} " + f"max={round(max_profit, 4)} offset={current_trailing_stop_positive_offset}") else: return None # Sinon : trailing actif dès le début # ----- 6) Condition de vente ----- - if 0 < profit <= trailing_stop and last_candle['mid'] < last_candle['sma5']: + if 0 < profit <= trailing_stop: # and last_candle['mid'] < last_candle['sma5']: # and profit > current_trailing_stop_positive_offset: self.pairs[pair]['force_buy'] = True print( - f"{current_time} Condition de vente trailing_stop={round(trailing_stop,4)} profit={round(profit, 4)} max={round(max_profit, 4)} " - f"{round(min(2, max_profit * (1 - current_trailing_stop_positive)), 4)}") + f"{current_time} Condition de vente trailing_stop={round(trailing_stop, 4)} profit={round(profit, 4)} max={round(max_profit, 4)} " + f"{round(min(2, max_profit * (1 - current_trailing_stop_positive)), 4)} offset={current_trailing_stop_positive_offset}") return f"stop_{count_of_buys}_{self.pairs[pair]['has_gain']}" return None @@ -1444,13 +1530,13 @@ class FrictradeLearning(IStrategy): df["weekly_low"] = ( df.groupby(["year", "week"])["low"] - .transform("min") - .shift(1) # décalé -> pas regarder la semaine en cours + .transform("min") + .shift(1) # décalé -> pas regarder la semaine en cours ) df["weekly_high"] = ( df.groupby(["year", "week"])["high"] - .transform("max") - .shift(1) + .transform("max") + .shift(1) ) # Définition simple d'une zone de demande hebdo : @@ -1463,13 +1549,13 @@ class FrictradeLearning(IStrategy): df["monthly_low"] = ( df.groupby(["year", "month"])["low"] - .transform("min") - .shift(1) # mois précédent uniquement + .transform("min") + .shift(1) # mois précédent uniquement ) df["monthly_high"] = ( df.groupby(["year", "month"])["high"] - .transform("max") - .shift(1) + .transform("max") + .shift(1) ) df["monthly_demand_zone_low"] = df["monthly_low"] @@ -1499,7 +1585,6 @@ class FrictradeLearning(IStrategy): # # return df - def rsi_trend_probability(self, dataframe, short=6, long=12): dataframe = dataframe.copy() @@ -1523,11 +1608,11 @@ class FrictradeLearning(IStrategy): return pd.to_datetime(x, utc=True) # suppose self.btc_ath_history exists (liste de dict) - def get_last_ath_before_candle(self, last_candle): + def get_last_ath_before_candle(self, date): - candle_date = self.to_utc_ts(last_candle['date']) # ou to_utc_ts(last_candle.name) + candle_date = self.to_utc_ts(date) # ou to_utc_ts(last_candle.name) best = None - for a in self.btc_ath_history: #getattr(self, "btc_ath_history", []): + for a in self.btc_ath_history: # getattr(self, "btc_ath_history", []): ath_date = self.to_utc_ts(a["date"]) if ath_date <= candle_date: if best is None or ath_date > best[0]: @@ -1539,7 +1624,7 @@ class FrictradeLearning(IStrategy): pd.set_option('display.max_rows', None) pd.set_option('display.max_columns', None) pd.set_option("display.width", 200) - path=self.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 @@ -1717,6 +1802,7 @@ class FrictradeLearning(IStrategy): 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) @@ -1801,7 +1887,6 @@ class FrictradeLearning(IStrategy): fig = plot_parallel_coordinate(study) fig.write_html(f"{self.path}/parallel_coordinates.html") - # 2️⃣ Sélection des features AVANT calibration sfm = SelectFromModel(self.train_model, threshold="median", prefit=True) selected_features = X_train.columns[sfm.get_support()] @@ -1897,7 +1982,6 @@ class FrictradeLearning(IStrategy): 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) # y = target binaire ou décimale # model = ton modèle entraîné (RandomForestClassifier ou Regressor) @@ -2123,7 +2207,7 @@ class FrictradeLearning(IStrategy): # Trace ou enregistre le graphique self.plot_threshold_analysis(y_valid, y_proba, step=0.05, - save_path=f"{self.path}/threshold_analysis.png") + save_path=f"{self.path}/threshold_analysis.png") # y_valid : vraies classes (0 / 1) # y_proba : probabilités de la classe 1 prédites par ton modèle @@ -2219,22 +2303,26 @@ class FrictradeLearning(IStrategy): 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("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') ] # É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) + # print("Colonnes utilisables pour le modèle :") + # print(usable_cols) # self.model_indicators = usable_cols return usable_cols - 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, @@ -2295,11 +2383,12 @@ class FrictradeLearning(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}"]) + 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}"]) # --- Distance à la moyenne mobile --- - dataframe[f"{name}{suffixe}_dist"] = (dataframe['close'] - dataframe[f"{name}{suffixe}"]) / dataframe[f"{name}{suffixe}"] - + dataframe[f"{name}{suffixe}_dist"] = (dataframe['close'] - dataframe[f"{name}{suffixe}"]) / dataframe[ + f"{name}{suffixe}"] # dérivée relative simple dataframe[d1_col] = (dataframe[name] - dataframe[name].shift(1)) / dataframe[name].shift(1) @@ -2380,6 +2469,44 @@ class FrictradeLearning(IStrategy): return dataframe + def calculateConfiance(self, informative): + df = informative.copy() + # ATR normalisé + df['atr_norm'] = talib.ATR(df['high'], df['low'], df['close'], length=14) / df['close'] + + # SMA200 & pente + df['sma200'] = talib.SMA(df['close'], 200) + df['sma200_slope'] = df['sma200'].diff() + + # drawdown + df['rolling_ath'] = df['close'].cummax() + df['drawdown'] = (df['close'] - df['rolling_ath']) / df['rolling_ath'] + + # volume spike + df['vol_spike'] = df['volume'] / df['volume'].rolling(20).mean() + + # RSI courts/longs + df['rsi14'] = talib.RSI(df['close'], 14) + df['rsi60'] = talib.RSI(df['close'], 60) + + # Scores normalisés + df['vol_score'] = 1 - np.clip(df['atr_norm'] / 0.05, 0, 1) + df['trend_score'] = 1 / (1 + np.exp(-df['sma200_slope'] * 150)) + df['dd_score'] = 1 - np.clip(abs(df['drawdown']) / 0.3, 0, 1) + df['volpanic_score'] = 1 - np.clip(df['vol_spike'] / 3, 0, 1) + df['rsi_score'] = 1 / (1 + np.exp(-(df['rsi14'] - df['rsi60']) / 10)) + + # Indice final + informative['confidence_index'] = ( + 0.25 * df['vol_score'] + + 0.25 * df['trend_score'] + + 0.20 * df['dd_score'] + + 0.15 * df['volpanic_score'] + + 0.15 * df['rsi_score'] + ) + + return informative + def calculModelInformative(self, informative): # préparation # print(df) @@ -2415,7 +2542,6 @@ class FrictradeLearning(IStrategy): # 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. @@ -2444,6 +2570,6 @@ class FrictradeLearning(IStrategy): dataframe_pruned = dataframe[kept_features].fillna(0) - print(f"⚡ Features conservées ({len(kept_features)} / {len(feature_columns)}): {kept_features}") + # print(f"⚡ Features conservées ({len(kept_features)} / {len(feature_columns)}): {kept_features}") return dataframe_pruned, kept_features diff --git a/plots/BTC/crash/BTC_rf_model.pkl b/plots/BTC/crash/BTC_rf_model.pkl index f211bcd..c190bd3 100644 Binary files a/plots/BTC/crash/BTC_rf_model.pkl and b/plots/BTC/crash/BTC_rf_model.pkl differ diff --git a/plots/BTC/crash/best_threshold.txt b/plots/BTC/crash/best_threshold.txt index d9dee50..5fa6ba2 100644 --- a/plots/BTC/crash/best_threshold.txt +++ b/plots/BTC/crash/best_threshold.txt @@ -1 +1 @@ -0.2788135593220339 \ No newline at end of file +0.14152542372881355 \ No newline at end of file diff --git a/plots/BTC/crash/confusion_matrix.png b/plots/BTC/crash/confusion_matrix.png index 08de185..68f4c91 100644 Binary files a/plots/BTC/crash/confusion_matrix.png and b/plots/BTC/crash/confusion_matrix.png differ diff --git a/plots/BTC/crash/f1_threshold.png b/plots/BTC/crash/f1_threshold.png index cc0767e..5132c4c 100644 Binary files a/plots/BTC/crash/f1_threshold.png and b/plots/BTC/crash/f1_threshold.png differ diff --git a/plots/BTC/crash/indicators_vs_prediction.png b/plots/BTC/crash/indicators_vs_prediction.png new file mode 100644 index 0000000..ad737da Binary files /dev/null and b/plots/BTC/crash/indicators_vs_prediction.png differ diff --git a/plots/BTC/crash/loss.png b/plots/BTC/crash/loss.png index 8d31a4c..04d6886 100644 Binary files a/plots/BTC/crash/loss.png and b/plots/BTC/crash/loss.png differ diff --git a/plots/BTC/crash/optimization_history.html b/plots/BTC/crash/optimization_history.html index 865dba3..08fae7a 100644 --- a/plots/BTC/crash/optimization_history.html +++ b/plots/BTC/crash/optimization_history.html @@ -3883,6 +3883,6 @@ maplibre-gl/dist/maplibre-gl.js: window.Plotly = Plotly; return Plotly; -}));
+}));