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 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'):
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% 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
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,