From 54d028d4f8c5fb096f2054f9b2ad17dec2eb7896 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Delacotte?= Date: Fri, 19 Dec 2025 20:06:24 +0100 Subject: [PATCH] FrictradeLearning.py gestion steps de mises adjust --- FrictradeLearning.py | 161 ++++++++++++++++++++++--------------------- 1 file changed, 84 insertions(+), 77 deletions(-) diff --git a/FrictradeLearning.py b/FrictradeLearning.py index f71a1f8..63fd7bf 100644 --- a/FrictradeLearning.py +++ b/FrictradeLearning.py @@ -11,6 +11,8 @@ import os from datetime import datetime from datetime import timezone +from datetime import timedelta + from typing import Optional import freqtrade.vendor.qtpylib.indicators as qtpylib @@ -128,6 +130,7 @@ class FrictradeLearning(IStrategy): pair: { "first_price": 0, "last_price": 0.0, + 'min_buy_price': 999999999999999.5, "last_min": 999999999999999.5, "last_max": 0, "trade_info": {}, @@ -177,7 +180,7 @@ class FrictradeLearning(IStrategy): "note": "pic oct. 2025 (source agrégée, à vérifier selon l'exchange)"} ] - def dynamic_trailing_offset(self, pair, price, ath, count_of_buys, max_dca=5): + def dynamic_trailing_offset(self, pair, stake, price, ath, count_of_buys, max_dca=5): # dd_ath = (ath - price) / ath # dd_ath = max(0.0, min(dd_ath, 0.5)) # @@ -189,7 +192,20 @@ class FrictradeLearning(IStrategy): # OFFSET_MIN = self.offset_min.value # OFFSET_MAX = self.offset_min.value + self.offset_max.value - return self.pairs[pair]['total_amount'] / 100 # OFFSET_MIN + breathing_score * (OFFSET_MAX - OFFSET_MIN) + return stake / 100 # OFFSET_MIN + breathing_score * (OFFSET_MAX - OFFSET_MIN) + + def cooldown_from_heat(self, score): + + if score < 0.05: + return timedelta(minutes=0) + + elif score < 0.25: + return timedelta(minutes=30) + + elif score < 0.5: + return timedelta(hours=2) + else: + return timedelta(hours=4) 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: @@ -207,17 +223,24 @@ class FrictradeLearning(IStrategy): allow_to_buy = True # (condition and not self.pairs[pair]['stop']) | (entry_tag == 'force_entry') + cooldown = self.cooldown_from_heat(last_candle['heat_score']) + + if self.pairs[pair]['last_date'] != 0 and cooldown.total_seconds() > 0: + if current_time < self.pairs[pair]['last_date'] + cooldown: + allow_to_buy = False + if allow_to_buy: self.trades = list() self.pairs[pair]['first_price'] = rate self.pairs[pair]['last_price'] = rate + self.pairs[pair]['min_buy_price'] = min(rate, self.pairs[pair]['min_buy_price']) self.pairs[pair]['max_touch'] = last_candle['close'] self.pairs[pair]['last_candle'] = last_candle self.pairs[pair]['count_of_buys'] = 1 self.pairs[pair]['current_profit'] = 0 self.pairs[pair]['last_max'] = max(last_candle['close'], self.pairs[pair]['last_max']) self.pairs[pair]['last_min'] = min(last_candle['close'], self.pairs[pair]['last_min']) - + self.pairs[pair]['min_buy_price'] = rate dispo = round(self.wallets.get_available_stake_amount()) self.printLineLog() @@ -240,6 +263,9 @@ class FrictradeLearning(IStrategy): buys=1, stake=round(stake_amount, 2) ) + else: + self.printLog( + f"{current_time} BUY triggered for {pair} (cooldown={cooldown} minutes={minutes} percent={round(last_candle['hapercent'], 4)}) but condition blocked") return allow_to_buy @@ -331,6 +357,9 @@ class FrictradeLearning(IStrategy): self.pairs[pair]['last_price'] = 0 self.pairs[pair]['last_date'] = current_time self.pairs[pair]['current_trade'] = None + self.pairs[pair]['min_buy_price'] = 100000000000000 + self.pairs[pair]['dca_thresholds'] = {} + self.pairs[pair]['mises'] = {} else: self.printLog( f"{current_time} SELL triggered for {pair} ({exit_reason} profit={profit} minutes={minutes} percent={last_candle['hapercent']}) but condition blocked") @@ -652,6 +681,7 @@ class FrictradeLearning(IStrategy): # status=closed, date=2024-08-26 02:20:11) dataframe['last_price'] = buy.price self.pairs[pair]['last_price'] = buy.price + self.pairs[pair]['min_buy_price'] = min(buy.price, self.pairs[pair]['min_buy_price']) count = count + 1 amount += buy.price * buy.filled self.pairs[pair]['count_of_buys'] = count @@ -775,8 +805,6 @@ class FrictradeLearning(IStrategy): 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) --- dataframe['vol_24'] = dataframe['percent'].rolling(24).std() @@ -857,6 +885,17 @@ class FrictradeLearning(IStrategy): # Non utilisé dans le modèle dataframe['min60'] = talib.MIN(dataframe['mid'], timeperiod=60) + self.calculeDerivees(dataframe, 'sma12', ema_period=6) + self.calculeDerivees(dataframe, 'sma5', ema_period=3) + + horizon = 180 + dataframe['price_change'] = (dataframe['close'] - dataframe['close'].shift(horizon)) / dataframe['close'].shift(horizon) + # dataframe['rsi_delta'] = dataframe['rsi'] - dataframe['rsi'].shift(horizon) + + dataframe['price_score'] = (dataframe['price_change'] / 0.05).clip(0, 2) + # dataframe['rsi_score'] = (dataframe['rsi_delta'] / 15).clip(0, 2) + + dataframe['heat_score'] = talib.MAX(dataframe['price_score'], timeperiod=horizon) #+ dataframe['rsi_score'] # val = 90000 # steps = 12 @@ -916,28 +955,44 @@ class FrictradeLearning(IStrategy): if len(self.pairs[pair]['dca_thresholds']) == 0: self.calculateStepsDcaThresholds(last_candle, pair) + if self.pairs[pair]['count_of_buys']: + dca_threshold = self.pairs[pair]['dca_thresholds'][min(self.pairs[pair]['count_of_buys'] - 1, len(self.pairs[pair]['dca_thresholds']) - 1)] + print(f"count_of_buys={self.pairs[pair]['count_of_buys']} dca_threshold={dca_threshold} {self.pairs[pair]['dca_thresholds']}") + print(f"val={val} dca={self.pairs[pair]['dca_thresholds']} ath={self.pairs[pair]['last_ath']} first_price={self.pairs[pair]['first_price']}") - if self.dp and self.pairs[pair]['first_price'] > 0: + if self.dp and val > 0: if self.dp.runmode.value in ('live', 'dry_run'): - full, mises, steps = self.calculateMises(pair, self.pairs[pair]['last_ath'], val) + if len(self.pairs[pair]['mises']) == 0: + full, mises, steps = self.calculateMises(pair, self.pairs[pair]['last_ath'], val) + else: + mises = self.pairs[pair]['mises'] + steps = len(self.pairs[pair]['mises']) # 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: + if val and len(self.pairs[pair]['dca_thresholds']) > 0 and len(mises) > 0 : print(self.pairs[pair]['dca_thresholds']) count = 0 pct = 0 dataframe = dataframe.copy() + total_stake = 0 + loss_amount = 0 + dca_previous = 0 for dca in self.pairs[pair]['dca_thresholds']: stake = mises[count] + total_stake += stake pct += dca - offset = self.dynamic_trailing_offset(pair, price=val, ath=ath, count_of_buys=count) + loss_amount += total_stake * dca_previous + offset = self.dynamic_trailing_offset(pair, total_stake, price=val, ath=ath, count_of_buys=count) - if count == self.pairs[pair]['count_of_buys']: + if count == self.pairs[pair]['count_of_buys'] - self.pairs[pair]['has_gain']: print(f"next_buy={round(val * (1 - pct),1)} count={count} pct={round(pct, 4)}") dataframe[f"next_buy"] = val * (1 - pct) count += 1 print( - f"stake={round(stake, 1)} count={count} pct={round(pct, 4)} offset={round(offset, 1)} next_buy={round(val * (1 - pct), 2)}") + f"stake={round(stake, 1)} total_stake={round(total_stake, 1)} count={count} " + f"pct={round(pct, 4)} offset={round(offset, 1)} next_buy={round(val * (1 - pct), 2)} " + f"loss_amount={round(loss_amount, 2)} pct_average={round(loss_amount / total_stake, 3)}") + dca_previous = dca return dataframe @@ -997,7 +1052,9 @@ class FrictradeLearning(IStrategy): dataframe.loc[ (dataframe["ml_prob"].shift(1) < dataframe["ml_prob"]) & (dataframe['sma24_deriv1'] > 0) - & (dataframe['sma12_deriv1'] > 0) + & (dataframe['sma5_deriv1'] > 0) + & (dataframe['sma5_deriv2'] > 0) + & (dataframe['rsi'] < 77) # & (dataframe['open'] < dataframe['max180'] * 0.997) # & (dataframe['min180'].shift(3) == dataframe['min180']) , ['enter_long', 'enter_tag'] @@ -1228,69 +1285,17 @@ class FrictradeLearning(IStrategy): # self.printLog("skip dataframe") return None - # Dernier prix d'achat réel (pas le prix moyen) - last_fill_price = self.pairs[trade.pair]['last_price'] # trade.open_rate # remplacé juste après ↓ - - # if len(trade.orders) > 0: - # # On cherche le dernier BUY exécuté - # buy_orders = [o for o in trade.orders if o.is_buy and o.status == "closed"] - # if buy_orders: - # last_fill_price = buy_orders[-1].price - - # baisse relative - # if minutes % 60 == 0: - # 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'] - - # steps = self.approx_value(last_candle['mid'], ath) - # dca_thresholds = split_ratio_one_third((last_candle['mid'] - (ath * self.allow_decrease_rate.value)) / last_candle['mid'], steps) #((last_candle['mid'] - (ath * self.allow_decrease_rate.value)) / 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)] + dca_threshold = self.pairs[pair]['dca_thresholds'][min(count_of_buys - 1, len(self.pairs[pair]['dca_thresholds']) - 1)] - # 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)}") + # Dernier prix d'achat réel (pas le prix moyen) + last_fill_price = self.pairs[trade.pair]['last_price'] decline = (last_fill_price - current_rate) / last_fill_price increase = - decline - # if decline >= self.dca_threshold: - # # Exemple : on achète 50% du montant du dernier trade - # last_amount = buy_orders[-1].amount if buy_orders else 0 - # 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['date'])) - # - # # --- 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 force = False #hours > self.hours_force.value and last_candle[self.indic_1h_force_buy.value] > 0 condition = last_candle['percent'] > 0 \ @@ -1299,6 +1304,8 @@ class FrictradeLearning(IStrategy): if ((force or decline >= dca_threshold) and condition): try: + print(f"decline={decline} last_fill_price={last_fill_price} current_rate={current_rate}") + if self.pairs[pair]['has_gain'] and profit > 0: self.pairs[pair]['force_sell'] = True self.pairs[pair]['previous_profit'] = profit @@ -1331,6 +1338,7 @@ class FrictradeLearning(IStrategy): self.pairs[trade.pair]['last_price'] = current_rate self.pairs[trade.pair]['max_touch'] = last_candle['close'] self.pairs[trade.pair]['last_candle'] = last_candle + self.pairs[trade.pair]['min_buy_price'] = min(current_rate, self.pairs[trade.pair]['min_buy_price']) # df = pd.DataFrame.from_dict(self.pairs, orient='index') # colonnes_a_exclure = ['last_candle', 'stop', @@ -1349,8 +1357,10 @@ class FrictradeLearning(IStrategy): 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: + and last_candle['sma5_deriv1'] > 0 and last_candle['sma5_deriv2'] > 0 and last_candle['max_rsi_12'] < 88: try: + print(f"decline={decline} last_fill_price={last_fill_price} current_rate={current_rate}") + self.pairs[pair]['previous_profit'] = profit stake_amount = max(10, min(self.wallets.get_available_stake_amount(), self.adjust_stake_amount(pair, last_candle))) @@ -1375,6 +1385,7 @@ class FrictradeLearning(IStrategy): self.pairs[trade.pair]['last_price'] = current_rate self.pairs[trade.pair]['max_touch'] = last_candle['close'] self.pairs[trade.pair]['last_candle'] = last_candle + self.pairs[trade.pair]['min_buy_price'] = min(current_rate, self.pairs[trade.pair]['min_buy_price']) return stake_amount return None except Exception as exception: @@ -1439,7 +1450,7 @@ 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(pair, price=current_rate, + current_trailing_stop_positive_offset = self.dynamic_trailing_offset(pair, self.pairs[pair]['total_amount'], price=current_rate, ath=self.pairs[pair]['last_ath'], count_of_buys=count_of_buys) @@ -1482,11 +1493,12 @@ class FrictradeLearning(IStrategy): if profit < 0: return None - if last_candle['rsi'] > 90: + if last_candle['max_rsi_24'] > 88 and last_candle['hapercent'] < 0\ + and last_candle['sma5_deriv2'] < -0.1: 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_deriv1'] > 0: # 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): @@ -2361,11 +2373,6 @@ class FrictradeLearning(IStrategy): return selected_corr - def calculateDerivation(self, dataframe, window=12, suffixe='', timeframe='5m'): - dataframe[f"mid_smooth{suffixe}"] = dataframe['mid'].rolling(window).mean() - dataframe = self.calculeDerivees(dataframe, f"mid_smooth{suffixe}", ema_period=window) - return dataframe - def calculeDerivees( self, dataframe: pd.DataFrame,