FrictradeLearning.py gestion steps de mises adjust

This commit is contained in:
Jérôme Delacotte
2025-12-19 20:06:24 +01:00
parent 17d8b7ccbc
commit 54d028d4f8

View File

@@ -11,6 +11,8 @@ import os
from datetime import datetime from datetime import datetime
from datetime import timezone from datetime import timezone
from datetime import timedelta
from typing import Optional from typing import Optional
import freqtrade.vendor.qtpylib.indicators as qtpylib import freqtrade.vendor.qtpylib.indicators as qtpylib
@@ -128,6 +130,7 @@ class FrictradeLearning(IStrategy):
pair: { pair: {
"first_price": 0, "first_price": 0,
"last_price": 0.0, "last_price": 0.0,
'min_buy_price': 999999999999999.5,
"last_min": 999999999999999.5, "last_min": 999999999999999.5,
"last_max": 0, "last_max": 0,
"trade_info": {}, "trade_info": {},
@@ -177,7 +180,7 @@ class FrictradeLearning(IStrategy):
"note": "pic oct. 2025 (source agrégée, à vérifier selon l'exchange)"} "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 = (ath - price) / ath
# dd_ath = max(0.0, min(dd_ath, 0.5)) # dd_ath = max(0.0, min(dd_ath, 0.5))
# #
@@ -189,7 +192,20 @@ class FrictradeLearning(IStrategy):
# OFFSET_MIN = self.offset_min.value # OFFSET_MIN = self.offset_min.value
# OFFSET_MAX = self.offset_min.value + self.offset_max.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, 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:
@@ -207,17 +223,24 @@ class FrictradeLearning(IStrategy):
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')
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: if allow_to_buy:
self.trades = list() self.trades = list()
self.pairs[pair]['first_price'] = rate self.pairs[pair]['first_price'] = rate
self.pairs[pair]['last_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]['max_touch'] = last_candle['close']
self.pairs[pair]['last_candle'] = last_candle self.pairs[pair]['last_candle'] = last_candle
self.pairs[pair]['count_of_buys'] = 1 self.pairs[pair]['count_of_buys'] = 1
self.pairs[pair]['current_profit'] = 0 self.pairs[pair]['current_profit'] = 0
self.pairs[pair]['last_max'] = max(last_candle['close'], self.pairs[pair]['last_max']) 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]['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()) dispo = round(self.wallets.get_available_stake_amount())
self.printLineLog() self.printLineLog()
@@ -240,6 +263,9 @@ class FrictradeLearning(IStrategy):
buys=1, buys=1,
stake=round(stake_amount, 2) 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 return allow_to_buy
@@ -331,6 +357,9 @@ class FrictradeLearning(IStrategy):
self.pairs[pair]['last_price'] = 0 self.pairs[pair]['last_price'] = 0
self.pairs[pair]['last_date'] = current_time self.pairs[pair]['last_date'] = current_time
self.pairs[pair]['current_trade'] = None self.pairs[pair]['current_trade'] = None
self.pairs[pair]['min_buy_price'] = 100000000000000
self.pairs[pair]['dca_thresholds'] = {}
self.pairs[pair]['mises'] = {}
else: else:
self.printLog( self.printLog(
f"{current_time} SELL triggered for {pair} ({exit_reason} profit={profit} minutes={minutes} percent={last_candle['hapercent']}) but condition blocked") 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) # status=closed, date=2024-08-26 02:20:11)
dataframe['last_price'] = buy.price dataframe['last_price'] = buy.price
self.pairs[pair]['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 count = count + 1
amount += buy.price * buy.filled amount += buy.price * buy.filled
self.pairs[pair]['count_of_buys'] = count self.pairs[pair]['count_of_buys'] = count
@@ -775,8 +805,6 @@ class FrictradeLearning(IStrategy):
close=dataframe['sma24'], volume=dataframe['volume'].rolling(24).sum() close=dataframe['sma24'], volume=dataframe['volume'].rolling(24).sum()
).on_balance_volume() ).on_balance_volume()
# self.calculeDerivees(dataframe, 'obv5', ema_period=5)
# --- Volatilité récente (écart-type des rendements) --- # --- Volatilité récente (écart-type des rendements) ---
dataframe['vol_24'] = dataframe['percent'].rolling(24).std() dataframe['vol_24'] = dataframe['percent'].rolling(24).std()
@@ -857,6 +885,17 @@ class FrictradeLearning(IStrategy):
# Non utilisé dans le modèle # Non utilisé dans le modèle
dataframe['min60'] = talib.MIN(dataframe['mid'], timeperiod=60) 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 # val = 90000
# steps = 12 # steps = 12
@@ -916,28 +955,44 @@ class FrictradeLearning(IStrategy):
if len(self.pairs[pair]['dca_thresholds']) == 0: if len(self.pairs[pair]['dca_thresholds']) == 0:
self.calculateStepsDcaThresholds(last_candle, pair) 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']}") 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'): if self.dp.runmode.value in ('live', 'dry_run'):
if len(self.pairs[pair]['mises']) == 0:
full, mises, steps = self.calculateMises(pair, self.pairs[pair]['last_ath'], val) 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)) # 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']) print(self.pairs[pair]['dca_thresholds'])
count = 0 count = 0
pct = 0 pct = 0
dataframe = dataframe.copy() dataframe = dataframe.copy()
total_stake = 0
loss_amount = 0
dca_previous = 0
for dca in self.pairs[pair]['dca_thresholds']: for dca in self.pairs[pair]['dca_thresholds']:
stake = mises[count] stake = mises[count]
total_stake += stake
pct += dca 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)}") print(f"next_buy={round(val * (1 - pct),1)} count={count} pct={round(pct, 4)}")
dataframe[f"next_buy"] = val * (1 - pct) dataframe[f"next_buy"] = val * (1 - pct)
count += 1 count += 1
print( 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 return dataframe
@@ -997,7 +1052,9 @@ class FrictradeLearning(IStrategy):
dataframe.loc[ dataframe.loc[
(dataframe["ml_prob"].shift(1) < dataframe["ml_prob"]) (dataframe["ml_prob"].shift(1) < dataframe["ml_prob"])
& (dataframe['sma24_deriv1'] > 0) & (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['open'] < dataframe['max180'] * 0.997)
# & (dataframe['min180'].shift(3) == dataframe['min180']) # & (dataframe['min180'].shift(3) == dataframe['min180'])
, ['enter_long', 'enter_tag'] , ['enter_long', 'enter_tag']
@@ -1228,69 +1285,17 @@ class FrictradeLearning(IStrategy):
# self.printLog("skip dataframe") # self.printLog("skip dataframe")
return None 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 # 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: if len(self.pairs[pair]['dca_thresholds']) == 0:
self.calculateStepsDcaThresholds(last_candle, pair) self.calculateStepsDcaThresholds(last_candle, pair)
dca_threshold = self.pairs[pair]['dca_thresholds'][ dca_threshold = self.pairs[pair]['dca_thresholds'][min(count_of_buys - 1, len(self.pairs[pair]['dca_thresholds']) - 1)]
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 decline = (last_fill_price - current_rate) / last_fill_price
increase = - decline 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% dun 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 # FIN ########################## ALGO ATH
force = False #hours > self.hours_force.value and last_candle[self.indic_1h_force_buy.value] > 0 force = False #hours > self.hours_force.value and last_candle[self.indic_1h_force_buy.value] > 0
condition = last_candle['percent'] > 0 \ condition = last_candle['percent'] > 0 \
@@ -1299,6 +1304,8 @@ class FrictradeLearning(IStrategy):
if ((force or decline >= dca_threshold) and condition): if ((force or decline >= dca_threshold) and condition):
try: try:
print(f"decline={decline} last_fill_price={last_fill_price} current_rate={current_rate}")
if self.pairs[pair]['has_gain'] and profit > 0: if self.pairs[pair]['has_gain'] and profit > 0:
self.pairs[pair]['force_sell'] = True self.pairs[pair]['force_sell'] = True
self.pairs[pair]['previous_profit'] = profit 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]['last_price'] = current_rate
self.pairs[trade.pair]['max_touch'] = last_candle['close'] self.pairs[trade.pair]['max_touch'] = last_candle['close']
self.pairs[trade.pair]['last_candle'] = last_candle 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') # df = pd.DataFrame.from_dict(self.pairs, orient='index')
# colonnes_a_exclure = ['last_candle', 'stop', # colonnes_a_exclure = ['last_candle', 'stop',
@@ -1349,8 +1357,10 @@ class FrictradeLearning(IStrategy):
increase_dca_threshold = 0.003 increase_dca_threshold = 0.003
if current_profit > increase_dca_threshold \ if current_profit > increase_dca_threshold \
and (increase >= increase_dca_threshold and self.wallets.get_available_stake_amount() > 0) \ 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: try:
print(f"decline={decline} last_fill_price={last_fill_price} current_rate={current_rate}")
self.pairs[pair]['previous_profit'] = profit self.pairs[pair]['previous_profit'] = profit
stake_amount = max(10, min(self.wallets.get_available_stake_amount(), stake_amount = max(10, min(self.wallets.get_available_stake_amount(),
self.adjust_stake_amount(pair, last_candle))) 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]['last_price'] = current_rate
self.pairs[trade.pair]['max_touch'] = last_candle['close'] self.pairs[trade.pair]['max_touch'] = last_candle['close']
self.pairs[trade.pair]['last_candle'] = last_candle 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 stake_amount
return None return None
except Exception as exception: 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_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.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'], ath=self.pairs[pair]['last_ath'],
count_of_buys=count_of_buys) count_of_buys=count_of_buys)
@@ -1482,11 +1493,12 @@ class FrictradeLearning(IStrategy):
if profit < 0: if profit < 0:
return None 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']}" return f"rsi_{count_of_buys}_{self.pairs[pair]['has_gain']}"
# if last_candle['sma12'] > last_candle['sma24']: # and last_candle['rsi'] < 85: if last_candle['sma12_deriv1'] > 0: # and last_candle['rsi'] < 85:
# return None return None
# if last_candle['sma24_deriv1'] > 0 : #and minutes < 180 and baisse < 30: # and last_candle['sma5_deriv1'] > -0.15: # if last_candle['sma24_deriv1'] > 0 : #and minutes < 180 and baisse < 30: # and last_candle['sma5_deriv1'] > -0.15:
# if (minutes < 180): # if (minutes < 180):
@@ -2361,11 +2373,6 @@ class FrictradeLearning(IStrategy):
return selected_corr 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( def calculeDerivees(
self, self,
dataframe: pd.DataFrame, dataframe: pd.DataFrame,