From b270b3b2836d5a5a60cfb3cf09a1f089d2795a66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Delacotte?= Date: Fri, 28 Nov 2025 20:23:20 +0100 Subject: [PATCH] Frictrade --- Frictrade.py | 57 +++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 47 insertions(+), 10 deletions(-) diff --git a/Frictrade.py b/Frictrade.py index 5ac5a0b..d74ccfd 100644 --- a/Frictrade.py +++ b/Frictrade.py @@ -41,7 +41,7 @@ RESET = "\033[0m" class Frictrade(IStrategy): - startup_candle_count = 60 * 24 + startup_candle_count = 180 # ROI table: minimal_roi = { @@ -103,6 +103,19 @@ class Frictrade(IStrategy): trades = list() max_profit_pairs = {} + btc_ath_history = [ + {"date": "2011-06-09", "price_usd": 26.15, "note": "pic 2011 (early breakout)"}, + {"date": "2013-11-29", "price_usd": 1132.00, "note": "bull run fin 2013"}, + {"date": "2017-12-17", "price_usd": 19783.00, "note": "ATH décembre 2017 (crypto bubble)"}, + {"date": "2020-12-31", "price_usd": 29001.72, "note": "fin 2020, nouveau record après accumulation)"}, + {"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": "2025-07-11", "price_usd": 118755.00, "note": "pic juillet 2025 (valeur rapportée par la presse)"}, + {"date": "2025-10-06", "price_usd": 126198.07, + "note": "pic oct. 2025 (source agrégée, à vérifier selon l'exchange)"} + ] + 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: @@ -292,7 +305,7 @@ class Frictrade(IStrategy): if self.columns_logged % 10 == 0: 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_max':>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" ) self.printLineLog() @@ -539,13 +552,18 @@ class Frictrade(IStrategy): # return adjusted_stake_amount def adjust_stake_amount(self, pair: str, last_candle: DataFrame): + + ath = max(self.pairs[pair]['last_max'], self.get_last_ath_before_candle(last_candle)) + + ath_dist = 100 * (ath - last_candle["mid"]) / ath + # 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}") + 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 = 2 * self.config.get('stake_amount') # exemple fraction du portefeuille; adapte selon ton code # new stake proportionnel à mult @@ -612,7 +630,7 @@ class Frictrade(IStrategy): # stake_amount = last_amount * current_rate * 0.5 # return stake_amount - condition = last_candle['hapercent'] > 0 and last_candle['sma60_deriv1'] > 0 + condition = last_candle['hapercent'] > 0 and last_candle['sma24_deriv1'] > 0 limit_buy = 40 if decline >= dca_threshold and condition: try: @@ -760,11 +778,11 @@ class Frictrade(IStrategy): if zone == 0: current_trailing_stop_positive = self.trailing_stop_positive current_trailing_stop_positive_offset = self.trailing_stop_positive_offset * 2 + if minutes > 1440: + current_trailing_only_offset_is_reached = False + current_trailing_stop_positive_offset = self.trailing_stop_positive_offset # if zone == 1: - - if last_candle['sma24_deriv1'] > 0: # and last_candle['sma5_deriv1'] > -0.15: - return None # ----- 5) Calcul du trailing stop dynamique ----- # Exemple : offset=0.321 => stop à +24.8% @@ -773,7 +791,7 @@ class Frictrade(IStrategy): if max_profit: baisse = (max_profit - profit) / max_profit - if minutes % 15 == 0: + if minutes % 12 == 0: self.log_trade( last_candle=last_candle, date=current_time, @@ -781,12 +799,15 @@ class Frictrade(IStrategy): dispo=dispo, pair=pair, rate=last_candle['close'], - trade_type=f"{round(profit, 2)} {round(max_profit, 2)} {round(trailing_stop,2)}", + 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['sma24_deriv1'] > 0 and minutes < 180: # and last_candle['sma5_deriv1'] > -0.15: + return None + # ----- 4) OFFSET : faut-il attendre de dépasser trailing_stop_positive_offset ? ----- if current_trailing_only_offset_is_reached: # Max profit pas atteint ET perte < 2 * current_trailing_stop_positive @@ -797,7 +818,7 @@ class Frictrade(IStrategy): # ----- 6) Condition de vente ----- if profit > 0 and profit <= trailing_stop and last_candle['mid'] < last_candle['sma5']: - return f"stop_{count_of_buys}" + return f"stop_{count_of_buys}_{self.pairs[pair]['has_gain']}" return None def informative_pairs(self): @@ -892,3 +913,19 @@ class Frictrade(IStrategy): return dataframe + import pandas as pd + + def to_utc_ts(self, x): + 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): + 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", []): + ath_date = self.to_utc_ts(a["date"]) + if ath_date <= candle_date: + if best is None or ath_date > best[0]: + best = (ath_date, a["price_usd"]) + return best[1] if best is not None else None +