Frictrade

This commit is contained in:
Jérôme Delacotte
2025-11-28 20:23:20 +01:00
parent 340c651a8a
commit b270b3b283

View File

@@ -41,7 +41,7 @@ RESET = "\033[0m"
class Frictrade(IStrategy): class Frictrade(IStrategy):
startup_candle_count = 60 * 24 startup_candle_count = 180
# ROI table: # ROI table:
minimal_roi = { minimal_roi = {
@@ -103,6 +103,19 @@ class Frictrade(IStrategy):
trades = list() trades = list()
max_profit_pairs = {} 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, 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: current_time: datetime, entry_tag: Optional[str], **kwargs) -> bool:
@@ -292,7 +305,7 @@ class Frictrade(IStrategy):
if self.columns_logged % 10 == 0: if self.columns_logged % 10 == 0:
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_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" f"{'rsi':>6}" #|Distmax|s201d|s5_1d|s5_2d|s51h|s52h|smt1h|smt2h|tdc1d|tdc1h"
) )
self.printLineLog() self.printLineLog()
@@ -539,13 +552,18 @@ class Frictrade(IStrategy):
# return adjusted_stake_amount # return adjusted_stake_amount
def adjust_stake_amount(self, pair: str, last_candle: DataFrame): 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 # Calcule max/min 180
low180 = last_candle["min180"] low180 = last_candle["min180"]
high180 = last_candle["max180"] high180 = last_candle["max180"]
mult = 1 - ((last_candle["mid"] - low180) / (high180 - low180)) 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 = 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 base_size = 2 * self.config.get('stake_amount') # exemple fraction du portefeuille; adapte selon ton code
# new stake proportionnel à mult # new stake proportionnel à mult
@@ -612,7 +630,7 @@ class Frictrade(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['sma60_deriv1'] > 0 condition = last_candle['hapercent'] > 0 and last_candle['sma24_deriv1'] > 0
limit_buy = 40 limit_buy = 40
if decline >= dca_threshold and condition: if decline >= dca_threshold and condition:
try: try:
@@ -760,11 +778,11 @@ class Frictrade(IStrategy):
if zone == 0: if zone == 0:
current_trailing_stop_positive = self.trailing_stop_positive current_trailing_stop_positive = self.trailing_stop_positive
current_trailing_stop_positive_offset = self.trailing_stop_positive_offset * 2 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 zone == 1:
if last_candle['sma24_deriv1'] > 0: # and last_candle['sma5_deriv1'] > -0.15:
return None
# ----- 5) Calcul du trailing stop dynamique ----- # ----- 5) Calcul du trailing stop dynamique -----
# Exemple : offset=0.321 => stop à +24.8% # Exemple : offset=0.321 => stop à +24.8%
@@ -773,7 +791,7 @@ class Frictrade(IStrategy):
if max_profit: if max_profit:
baisse = (max_profit - profit) / max_profit baisse = (max_profit - profit) / max_profit
if minutes % 15 == 0: if minutes % 12 == 0:
self.log_trade( self.log_trade(
last_candle=last_candle, last_candle=last_candle,
date=current_time, date=current_time,
@@ -781,12 +799,15 @@ class Frictrade(IStrategy):
dispo=dispo, dispo=dispo,
pair=pair, pair=pair,
rate=last_candle['close'], 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), profit=round(profit, 2),
buys=count_of_buys, buys=count_of_buys,
stake=0 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 ? ----- # ----- 4) OFFSET : faut-il attendre de dépasser trailing_stop_positive_offset ? -----
if current_trailing_only_offset_is_reached: if current_trailing_only_offset_is_reached:
# Max profit pas atteint ET perte < 2 * current_trailing_stop_positive # Max profit pas atteint ET perte < 2 * current_trailing_stop_positive
@@ -797,7 +818,7 @@ class Frictrade(IStrategy):
# ----- 6) Condition de vente ----- # ----- 6) Condition de vente -----
if profit > 0 and profit <= trailing_stop and last_candle['mid'] < last_candle['sma5']: 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 return None
def informative_pairs(self): def informative_pairs(self):
@@ -892,3 +913,19 @@ class Frictrade(IStrategy):
return dataframe 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