diff --git a/FrictradeLearning.py b/FrictradeLearning.py index 06fb9f2..5902809 100644 --- a/FrictradeLearning.py +++ b/FrictradeLearning.py @@ -119,6 +119,8 @@ class FrictradeLearning(IStrategy): -18: 0.30, } + allow_decrease_rate = 0.4 + # ROI table: minimal_roi = { "0": 10 @@ -168,7 +170,9 @@ class FrictradeLearning(IStrategy): 'total_amount': 0, 'has_gain': 0, 'force_sell': False, - 'force_buy': False + 'force_buy': False, + 'last_ath': 0, + 'dca_thresholds': {} } for pair in ["BTC/USDC", "ETH/USDC", "DOGE/USDC", "XRP/USDC", "SOL/USDC", "BTC/USDT", "ETH/USDT", "DOGE/USDT", "XRP/USDT", "SOL/USDT"] @@ -216,7 +220,6 @@ class FrictradeLearning(IStrategy): self.pairs[pair]['last_max'] = max(last_candle['close'], self.pairs[pair]['last_max']) self.pairs[pair]['last_min'] = min(last_candle['close'], self.pairs[pair]['last_min']) - dispo = round(self.wallets.get_available_stake_amount()) self.printLineLog() @@ -225,6 +228,8 @@ class FrictradeLearning(IStrategy): self.pairs[pair]['total_amount'] = stake_amount self.pairs[pair]['first_amount'] = stake_amount + self.calculateStepsDcaThresholds(last_candle, pair) + self.log_trade( last_candle=last_candle, date=current_time, @@ -240,6 +245,23 @@ class FrictradeLearning(IStrategy): return allow_to_buy + 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)] + + if self.pairs[pair]['last_ath'] == 0 : + ath = max(last_candle['mid'], self.get_last_ath_before_candle(last_candle)) + self.pairs[pair]['last_ath'] = ath + + steps = self.approx_value(last_candle['mid'], self.pairs[pair]['last_ath']) + self.pairs[pair]['dca_thresholds'] = split_ratio_one_third( + (last_candle['mid'] - (self.pairs[pair]['last_ath'] * (1 - self.allow_decrease_rate))) / last_candle['mid'], + steps) + print(f"val={last_candle['mid']} steps={steps} pct={(last_candle['mid'] - (self.pairs[pair]['last_ath'] * (1 - self.allow_decrease_rate))) / last_candle['mid']}") + print(self.pairs[pair]['dca_thresholds']) + def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount: float, rate: float, time_in_force: str, exit_reason: str, current_time, **kwargs, ) -> bool: @@ -381,7 +403,7 @@ 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}|{'mlprob':>6}" #|Distmax|s201d|s5_1d|s5_2d|s51h|s52h|smt1h|smt2h|tdc1d|tdc1h" + f"{'rsi':>6}|{'rsi_1h':>6}|{'rsi_1d':>6}|{'mlprob':>6}" #|Distmax|s201d|s5_1d|s5_2d|s51h|s52h|smt1h|smt2h|tdc1d|tdc1h" ) self.printLineLog() df = pd.DataFrame.from_dict(self.pairs, orient='index') @@ -565,8 +587,12 @@ class FrictradeLearning(IStrategy): filled_buys = trade.select_filled_orders('buy') count = 0 amount = 0 + min_price = 111111111111110; + max_price = 0; for buy in filled_buys: if count == 0: + min_price = min(min_price, buy.price) + max_price = max(max_price, buy.price) dataframe['first_price'] = buy.price self.pairs[pair]['first_buy'] = buy.price self.pairs[pair]['first_amount'] = buy.price * buy.filled @@ -782,6 +808,36 @@ class FrictradeLearning(IStrategy): # Non utilisé dans le modèle dataframe['min60'] = talib.MIN(dataframe['mid'], timeperiod=60) + # val = 90000 + # steps = 12 + # [0.018, 0.022, 0.025, 0.028, 0.032, 0.035, 0.038, 0.042, 0.045, 0.048, 0.052, 0.055] + + # val = 100000 + # steps = 20 + # [0.012, 0.014, 0.015, 0.016, 0.018, 0.019, 0.02, 0.022, 0.023, 0.024, 0.025, 0.027, 0.028, 0.029, 0.031, 0.032, + # 0.033, 0.035, 0.036, 0.037] + + # val = 110000 + # steps = 28 + # [0.01, 0.01, 0.011, 0.012, 0.013, 0.013, 0.014, 0.015, 0.015, 0.016, 0.017, 0.018, 0.018, 0.019, 0.02, 0.02, + # 0.021, 0.022, 0.023, 0.023, 0.024, 0.025, 0.025, 0.026, 0.027, 0.028, 0.028, 0.029] + + # val = 120000 + # steps = 35 + # [0.008, 0.009, 0.009, 0.01, 0.01, 0.011, 0.011, 0.012, 0.012, 0.013, 0.013, 0.014, 0.014, 0.015, 0.015, 0.016, + # 0.016, 0.017, 0.017, 0.018, 0.018, 0.019, 0.019, 0.019, 0.02, 0.02, 0.021, 0.021, 0.022, 0.022, 0.023, 0.023, + # 0.024, 0.024, 0.025] + + # 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)] + # + # 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))) / val}") + # dca = split_ratio_one_third((val - (126000 * (1 - self.allow_decrease_rate))) / 126000, steps) + # print(dca) return dataframe @@ -959,21 +1015,37 @@ class FrictradeLearning(IStrategy): # # return adjusted_stake_amount + def approx_value(self, x, X_max): + X_min = X_max * (1 - self.allow_decrease_rate) # 126198 * 0.4 = 75718,8 + Y_min = 1 + Y_max = 40 + 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 + 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'] ath = max(self.pairs[pair]['last_max'], self.get_last_ath_before_candle(last_candle)) + 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)) - base_stake = self.config.get('stake_amount') * (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"] @@ -995,8 +1067,6 @@ class FrictradeLearning(IStrategy): if trade.has_open_orders: # self.printLog("skip open orders") return None - if (self.wallets.get_available_stake_amount() < 10): # or trade.stake_amount >= max_stake: - return 0 dataframe, _ = self.dp.get_analyzed_dataframe(trade.pair, self.timeframe) last_candle = dataframe.iloc[-1].squeeze() @@ -1008,6 +1078,8 @@ class FrictradeLearning(IStrategy): hours_since_first_buy = (current_time - trade.open_date_utc).seconds / 3600.0 days_since_first_buy = (current_time - trade.open_date_utc).days hours = (current_time - trade.date_last_filled_utc).total_seconds() / 3600.0 + minutes = (current_time - trade.date_last_filled_utc).total_seconds() / 60.0 + count_of_buys = trade.nr_of_successful_entries current_time_utc = current_time.astimezone(timezone.utc) open_date = trade.open_date.astimezone(timezone.utc) @@ -1023,6 +1095,34 @@ class FrictradeLearning(IStrategy): if self.pairs[pair]['first_buy']: pct_first = self.getPctFirstBuy(pair, last_candle) + if profit > - self.pairs[pair]['first_amount'] and count_of_buys > 15 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 (self.wallets.get_available_stake_amount() < 10): # or trade.stake_amount >= max_stake: + return 0 + lim = 0.3 if (len(dataframe) < 1): # self.printLog("skip dataframe") @@ -1038,7 +1138,21 @@ class FrictradeLearning(IStrategy): # last_fill_price = buy_orders[-1].price # baisse relative - dca_threshold = 0.0025 * count_of_buys + if minutes % 60 == 0: + ath = max(self.pairs[pair]['last_max'], self.get_last_ath_before_candle(last_candle)) + self.pairs[pair]['last_ath'] = ath + else: + ath = self.pairs[pair]['last_ath'] + + # steps = self.approx_value(last_candle['mid'], ath) + + # dca_thresholds = split_ratio_one_third((last_candle['mid'] - (ath * self.allow_decrease_rate)) / last_candle['mid'], steps) #((last_candle['mid'] - (ath * self.allow_decrease_rate)) / steps) / last_candle['mid'] # 0.0025 + 0.0005 * count_of_buys + 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)] + + # print(f"{count_of_buys} {ath * (1 - self.allow_decrease_rate)} {round(last_candle['mid'], 2)} {round((last_candle['mid'] - (ath * self.allow_decrease_rate)) / last_candle['mid'], 2)} {steps} {round(dca_threshold, 4)}") decline = (last_fill_price - current_rate) / last_fill_price increase = - decline @@ -1078,21 +1192,21 @@ class FrictradeLearning(IStrategy): # return None # FIN ########################## ALGO ATH - + force = hours > 24 and last_candle['sma60_deriv1_1h'] > 0 condition = last_candle['percent'] > 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 # or (last_candle['close'] <= last_candle['min180'] and hours > 3) - if (decline >= dca_threshold) and condition: + if ((force or decline >= dca_threshold) and condition): try: if self.pairs[pair]['has_gain'] and profit > 0: self.pairs[pair]['force_sell'] = True self.pairs[pair]['previous_profit'] = profit return None - stake_amount = min(self.wallets.get_available_stake_amount(), self.adjust_stake_amount(pair, last_candle) / 2) + stake_amount = min(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}") if stake_amount > 0: self.pairs[pair]['previous_profit'] = profit @@ -1102,7 +1216,7 @@ class FrictradeLearning(IStrategy): self.log_trade( last_candle=last_candle, date=current_time, - action="🟧 Loss -", + action="🟧 " + ("Force" if force else 'Loss -'), dispo=dispo, pair=trade.pair, rate=current_rate, @@ -1130,7 +1244,8 @@ 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): + if current_profit > dca_threshold and (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))) @@ -1242,19 +1357,19 @@ class FrictradeLearning(IStrategy): if max_profit: baisse = (max_profit - profit) / max_profit - if minutes % 12 == 0: - self.log_trade( - last_candle=last_candle, - date=current_time, - action="🟢 CURRENT", #🔴 CURRENT" if self.pairs[pair]['stop'] or last_candle['stop_buying'] else " - dispo=dispo, - pair=pair, - rate=last_candle['close'], - trade_type=f"{round(profit, 2)} {round(max_profit, 2)} {round(trailing_stop,2)} {minutes}", - profit=round(profit, 2), - buys=count_of_buys, - stake=0 - ) + # if minutes % 12 == 0: + # self.log_trade( + # last_candle=last_candle, + # date=current_time, + # action="🟢 CURRENT", #🔴 CURRENT" if self.pairs[pair]['stop'] or last_candle['stop_buying'] else " + # dispo=dispo, + # pair=pair, + # rate=last_candle['close'], + # trade_type=f"{round(profit, 2)} {round(max_profit, 2)} {round(trailing_stop,2)} {minutes}", + # profit=round(profit, 2), + # buys=count_of_buys, + # stake=0 + # ) if last_candle['sma12'] > last_candle['sma24']: return None @@ -1275,6 +1390,7 @@ class FrictradeLearning(IStrategy): # ----- 6) Condition de vente ----- if 0 < profit <= trailing_stop and last_candle['mid'] < last_candle['sma5']: + self.pairs[pair]['force_buy'] = True return f"stop_{count_of_buys}_{self.pairs[pair]['has_gain']}" return None @@ -1377,6 +1493,7 @@ class FrictradeLearning(IStrategy): # suppose self.btc_ath_history exists (liste de dict) def get_last_ath_before_candle(self, last_candle): + candle_date = self.to_utc_ts(last_candle['date']) # ou to_utc_ts(last_candle.name) best = None for a in self.btc_ath_history: #getattr(self, "btc_ath_history", []): @@ -1420,7 +1537,7 @@ class FrictradeLearning(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["sma24"].shift(-13) - df["sma24"]) > 0).astype(int) + df['target'] = ((df["sma24"].shift(-13) - df["sma24"]) > 100).astype(int) df['target'] = df['target'].fillna(0).astype(int) # Corrélations triées par importance avec une colonne cible @@ -1545,11 +1662,11 @@ class FrictradeLearning(IStrategy): # ) 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), + 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", diff --git a/plots/BTC/BTC_rf_model.pkl b/plots/BTC/BTC_rf_model.pkl index 205757a..1a62f1d 100644 Binary files a/plots/BTC/BTC_rf_model.pkl and b/plots/BTC/BTC_rf_model.pkl differ diff --git a/plots/BTC/Courbe ROC.png b/plots/BTC/Courbe ROC.png index 8800986..c3c06c2 100644 Binary files a/plots/BTC/Courbe ROC.png and b/plots/BTC/Courbe ROC.png differ diff --git a/plots/BTC/Feature importances.png b/plots/BTC/Feature importances.png index 606f406..37a669f 100644 Binary files a/plots/BTC/Feature importances.png and b/plots/BTC/Feature importances.png differ diff --git a/plots/BTC/Importance des features.png b/plots/BTC/Importance des features.png index fc08c77..2a5babe 100644 Binary files a/plots/BTC/Importance des features.png and b/plots/BTC/Importance des features.png differ diff --git a/plots/BTC/Matrice de confusion.png b/plots/BTC/Matrice de confusion.png index 4b5e845..f6342da 100644 Binary files a/plots/BTC/Matrice de confusion.png and b/plots/BTC/Matrice de confusion.png differ diff --git a/plots/BTC/Matrice_de_correlation_temperature.png b/plots/BTC/Matrice_de_correlation_temperature.png index 762c659..bc2211a 100644 Binary files a/plots/BTC/Matrice_de_correlation_temperature.png and b/plots/BTC/Matrice_de_correlation_temperature.png differ diff --git a/plots/BTC/PartialDependenceDisplay.png b/plots/BTC/PartialDependenceDisplay.png index 27d0177..e368530 100644 Binary files a/plots/BTC/PartialDependenceDisplay.png and b/plots/BTC/PartialDependenceDisplay.png differ diff --git a/plots/BTC/Permutation feature importance.png b/plots/BTC/Permutation feature importance.png index 517fcc1..769f062 100644 Binary files a/plots/BTC/Permutation feature importance.png and b/plots/BTC/Permutation feature importance.png differ diff --git a/plots/BTC/optimization_history.html b/plots/BTC/optimization_history.html index 0c7ba93..fa11ac1 100644 --- a/plots/BTC/optimization_history.html +++ b/plots/BTC/optimization_history.html @@ -3883,6 +3883,6 @@ maplibre-gl/dist/maplibre-gl.js: window.Plotly = Plotly; return Plotly; -}));
+}));