FrictradeLearning.py Stoploss auto et gestion steps de mises adjust

This commit is contained in:
Jérôme Delacotte
2025-12-06 21:04:48 +01:00
parent fc6d516252
commit e8cdf10488
17 changed files with 155 additions and 38 deletions

View File

@@ -119,6 +119,8 @@ class FrictradeLearning(IStrategy):
-18: 0.30,
}
allow_decrease_rate = 0.4
# ROI table:
minimal_roi = {
"0": 10
@@ -168,7 +170,9 @@ class FrictradeLearning(IStrategy):
'total_amount': 0,
'has_gain': 0,
'force_sell': False,
'force_buy': False
'force_buy': False,
'last_ath': 0,
'dca_thresholds': {}
}
for pair in ["BTC/USDC", "ETH/USDC", "DOGE/USDC", "XRP/USDC", "SOL/USDC",
"BTC/USDT", "ETH/USDT", "DOGE/USDT", "XRP/USDT", "SOL/USDT"]
@@ -216,7 +220,6 @@ class FrictradeLearning(IStrategy):
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'])
dispo = round(self.wallets.get_available_stake_amount())
self.printLineLog()
@@ -225,6 +228,8 @@ class FrictradeLearning(IStrategy):
self.pairs[pair]['total_amount'] = stake_amount
self.pairs[pair]['first_amount'] = stake_amount
self.calculateStepsDcaThresholds(last_candle, pair)
self.log_trade(
last_candle=last_candle,
date=current_time,
@@ -240,6 +245,23 @@ class FrictradeLearning(IStrategy):
return allow_to_buy
def calculateStepsDcaThresholds(self, last_candle, pair):
def split_ratio_one_third(n, p):
a = n / (2 * p) # première valeur
d = n / (p * (p - 1)) # incrément
return [round(a + i * d, 3) for i in range(p)]
if self.pairs[pair]['last_ath'] == 0 :
ath = max(last_candle['mid'], self.get_last_ath_before_candle(last_candle))
self.pairs[pair]['last_ath'] = ath
steps = self.approx_value(last_candle['mid'], self.pairs[pair]['last_ath'])
self.pairs[pair]['dca_thresholds'] = split_ratio_one_third(
(last_candle['mid'] - (self.pairs[pair]['last_ath'] * (1 - self.allow_decrease_rate))) / last_candle['mid'],
steps)
print(f"val={last_candle['mid']} steps={steps} pct={(last_candle['mid'] - (self.pairs[pair]['last_ath'] * (1 - self.allow_decrease_rate))) / last_candle['mid']}")
print(self.pairs[pair]['dca_thresholds'])
def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount: float, rate: float,
time_in_force: str,
exit_reason: str, current_time, **kwargs, ) -> bool:
@@ -381,7 +403,7 @@ class FrictradeLearning(IStrategy):
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_min':>7}|{'Buys':>5}| {'Stake':>5} |"
f"{'rsi':>6}|{'mlprob':>6}" #|Distmax|s201d|s5_1d|s5_2d|s51h|s52h|smt1h|smt2h|tdc1d|tdc1h"
f"{'rsi':>6}|{'rsi_1h':>6}|{'rsi_1d':>6}|{'mlprob':>6}" #|Distmax|s201d|s5_1d|s5_2d|s51h|s52h|smt1h|smt2h|tdc1d|tdc1h"
)
self.printLineLog()
df = pd.DataFrame.from_dict(self.pairs, orient='index')
@@ -565,8 +587,12 @@ class FrictradeLearning(IStrategy):
filled_buys = trade.select_filled_orders('buy')
count = 0
amount = 0
min_price = 111111111111110;
max_price = 0;
for buy in filled_buys:
if count == 0:
min_price = min(min_price, buy.price)
max_price = max(max_price, buy.price)
dataframe['first_price'] = buy.price
self.pairs[pair]['first_buy'] = buy.price
self.pairs[pair]['first_amount'] = buy.price * buy.filled
@@ -782,6 +808,36 @@ class FrictradeLearning(IStrategy):
# Non utilisé dans le modèle
dataframe['min60'] = talib.MIN(dataframe['mid'], timeperiod=60)
# val = 90000
# steps = 12
# [0.018, 0.022, 0.025, 0.028, 0.032, 0.035, 0.038, 0.042, 0.045, 0.048, 0.052, 0.055]
# val = 100000
# steps = 20
# [0.012, 0.014, 0.015, 0.016, 0.018, 0.019, 0.02, 0.022, 0.023, 0.024, 0.025, 0.027, 0.028, 0.029, 0.031, 0.032,
# 0.033, 0.035, 0.036, 0.037]
# val = 110000
# steps = 28
# [0.01, 0.01, 0.011, 0.012, 0.013, 0.013, 0.014, 0.015, 0.015, 0.016, 0.017, 0.018, 0.018, 0.019, 0.02, 0.02,
# 0.021, 0.022, 0.023, 0.023, 0.024, 0.025, 0.025, 0.026, 0.027, 0.028, 0.028, 0.029]
# val = 120000
# steps = 35
# [0.008, 0.009, 0.009, 0.01, 0.01, 0.011, 0.011, 0.012, 0.012, 0.013, 0.013, 0.014, 0.014, 0.015, 0.015, 0.016,
# 0.016, 0.017, 0.017, 0.018, 0.018, 0.019, 0.019, 0.019, 0.02, 0.02, 0.021, 0.021, 0.022, 0.022, 0.023, 0.023,
# 0.024, 0.024, 0.025]
# def split_ratio_one_third(n, p):
# a = n / (2 * p) # première valeur
# d = n / (p * (p - 1)) # incrément
# return [round(a + i * d, 3) for i in range(p)]
#
# for val in range(90000, 130000, 10000):
# steps = self.approx_value(val, 126000)
# print(f"val={val} steps={steps} pct={(val - (126000 * (1 - self.allow_decrease_rate))) / val}")
# dca = split_ratio_one_third((val - (126000 * (1 - self.allow_decrease_rate))) / 126000, steps)
# print(dca)
return dataframe
@@ -959,21 +1015,37 @@ class FrictradeLearning(IStrategy):
#
# return adjusted_stake_amount
def approx_value(self, x, X_max):
X_min = X_max * (1 - self.allow_decrease_rate) # 126198 * 0.4 = 75718,8
Y_min = 1
Y_max = 40
a = (Y_max - Y_min) / (X_max - X_min) # 39 ÷ (126198 126198×0,6) = 0,000772595
b = Y_min - a * X_min # 1 (0,000772595 × 75718,8) = 38
y = a * x + b # 0,000772595 * 115000 - 38
return max(round(y), 1) # évite les valeurs négatives
def adjust_stake_amount(self, pair: str, last_candle: DataFrame):
if self.pairs[pair]['first_amount'] > 0:
return self.pairs[pair]['first_amount']
ath = max(self.pairs[pair]['last_max'], self.get_last_ath_before_candle(last_candle))
self.pairs[pair]['last_ath'] = ath
ath_dist = 100 * (ath - last_candle["mid"]) / ath
# ath_dist
# 0 ==> 1
# 20 ==> 1.5
# 40 ==> 2
# 50 * (1 + (ath_dist / 40))
base_stake = self.config.get('stake_amount') * (1 + (ath_dist / 40))
full = self.wallets.get_total_stake_amount()
steps = self.approx_value(last_candle['mid'], ath)
base_stake = full / steps
# base_stake = stake * (1 + (ath_dist / 40))
# Calcule max/min 180
low180 = last_candle["min180"]
@@ -995,8 +1067,6 @@ class FrictradeLearning(IStrategy):
if trade.has_open_orders:
# self.printLog("skip open orders")
return None
if (self.wallets.get_available_stake_amount() < 10): # or trade.stake_amount >= max_stake:
return 0
dataframe, _ = self.dp.get_analyzed_dataframe(trade.pair, self.timeframe)
last_candle = dataframe.iloc[-1].squeeze()
@@ -1008,6 +1078,8 @@ class FrictradeLearning(IStrategy):
hours_since_first_buy = (current_time - trade.open_date_utc).seconds / 3600.0
days_since_first_buy = (current_time - trade.open_date_utc).days
hours = (current_time - trade.date_last_filled_utc).total_seconds() / 3600.0
minutes = (current_time - trade.date_last_filled_utc).total_seconds() / 60.0
count_of_buys = trade.nr_of_successful_entries
current_time_utc = current_time.astimezone(timezone.utc)
open_date = trade.open_date.astimezone(timezone.utc)
@@ -1023,6 +1095,34 @@ class FrictradeLearning(IStrategy):
if self.pairs[pair]['first_buy']:
pct_first = self.getPctFirstBuy(pair, last_candle)
if profit > - self.pairs[pair]['first_amount'] and count_of_buys > 15 and last_candle['sma24_deriv1_1h'] < 0:
stake_amount = trade.stake_amount
self.pairs[pair]['previous_profit'] = profit
trade_type = "Sell " + (last_candle['enter_tag'] if last_candle['enter_long'] == 1 else '')
self.pairs[trade.pair]['count_of_buys'] += 1
self.pairs[pair]['total_amount'] = stake_amount
self.log_trade(
last_candle=last_candle,
date=current_time,
action="🟧 Sell +",
dispo=dispo,
pair=trade.pair,
rate=current_rate,
trade_type=trade_type,
profit=round(profit, 1),
buys=trade.nr_of_successful_entries + 1,
stake=round(stake_amount, 2)
)
self.pairs[trade.pair]['last_buy'] = current_rate
self.pairs[trade.pair]['max_touch'] = last_candle['close']
self.pairs[trade.pair]['last_candle'] = last_candle
return -stake_amount
if (self.wallets.get_available_stake_amount() < 10): # or trade.stake_amount >= max_stake:
return 0
lim = 0.3
if (len(dataframe) < 1):
# self.printLog("skip dataframe")
@@ -1038,7 +1138,21 @@ class FrictradeLearning(IStrategy):
# last_fill_price = buy_orders[-1].price
# baisse relative
dca_threshold = 0.0025 * count_of_buys
if minutes % 60 == 0:
ath = max(self.pairs[pair]['last_max'], self.get_last_ath_before_candle(last_candle))
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)) / last_candle['mid'], steps) #((last_candle['mid'] - (ath * self.allow_decrease_rate)) / 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)]
# print(f"{count_of_buys} {ath * (1 - self.allow_decrease_rate)} {round(last_candle['mid'], 2)} {round((last_candle['mid'] - (ath * self.allow_decrease_rate)) / last_candle['mid'], 2)} {steps} {round(dca_threshold, 4)}")
decline = (last_fill_price - current_rate) / last_fill_price
increase = - decline
@@ -1078,21 +1192,21 @@ class FrictradeLearning(IStrategy):
# return None
# FIN ########################## ALGO ATH
force = hours > 24 and last_candle['sma60_deriv1_1h'] > 0
condition = last_candle['percent'] > 0 and last_candle['sma24_deriv1'] > 0 \
and last_candle['close'] < self.pairs[pair]['first_buy']
# and last_candle['ml_prob'] > 0.65
limit_buy = 40
# or (last_candle['close'] <= last_candle['min180'] and hours > 3)
if (decline >= dca_threshold) and condition:
if ((force or decline >= dca_threshold) and condition):
try:
if self.pairs[pair]['has_gain'] and profit > 0:
self.pairs[pair]['force_sell'] = True
self.pairs[pair]['previous_profit'] = profit
return None
stake_amount = min(self.wallets.get_available_stake_amount(), self.adjust_stake_amount(pair, last_candle) / 2)
stake_amount = min(self.wallets.get_available_stake_amount(), self.adjust_stake_amount(pair, last_candle))
# print(f"profit={profit} previous={self.pairs[pair]['previous_profit']} count_of_buys={trade.nr_of_successful_entries}")
if stake_amount > 0:
self.pairs[pair]['previous_profit'] = profit
@@ -1102,7 +1216,7 @@ class FrictradeLearning(IStrategy):
self.log_trade(
last_candle=last_candle,
date=current_time,
action="🟧 Loss -",
action="🟧 " + ("Force" if force else 'Loss -'),
dispo=dispo,
pair=trade.pair,
rate=current_rate,
@@ -1130,7 +1244,8 @@ class FrictradeLearning(IStrategy):
self.printLog(exception)
return None
if current_profit > dca_threshold and (increase >= dca_threshold and self.wallets.get_available_stake_amount() > 0):
if current_profit > dca_threshold and (increase >= dca_threshold and self.wallets.get_available_stake_amount() > 0)\
and last_candle['rsi'] < 75:
try:
self.pairs[pair]['previous_profit'] = profit
stake_amount = max(20, min(self.wallets.get_available_stake_amount(), self.adjust_stake_amount(pair, last_candle)))
@@ -1242,19 +1357,19 @@ class FrictradeLearning(IStrategy):
if max_profit:
baisse = (max_profit - profit) / max_profit
if minutes % 12 == 0:
self.log_trade(
last_candle=last_candle,
date=current_time,
action="🟢 CURRENT", #🔴 CURRENT" if self.pairs[pair]['stop'] or last_candle['stop_buying'] else "
dispo=dispo,
pair=pair,
rate=last_candle['close'],
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 minutes % 12 == 0:
# self.log_trade(
# last_candle=last_candle,
# date=current_time,
# action="🟢 CURRENT", #🔴 CURRENT" if self.pairs[pair]['stop'] or last_candle['stop_buying'] else "
# dispo=dispo,
# pair=pair,
# rate=last_candle['close'],
# 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['sma12'] > last_candle['sma24']:
return None
@@ -1275,6 +1390,7 @@ class FrictradeLearning(IStrategy):
# ----- 6) Condition de vente -----
if 0 < profit <= trailing_stop and last_candle['mid'] < last_candle['sma5']:
self.pairs[pair]['force_buy'] = True
return f"stop_{count_of_buys}_{self.pairs[pair]['has_gain']}"
return None
@@ -1377,6 +1493,7 @@ class FrictradeLearning(IStrategy):
# 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", []):
@@ -1420,7 +1537,7 @@ class FrictradeLearning(IStrategy):
# 3⃣ Créer la cible : 1 si le prix monte dans les prochaines bougies
# df['target'] = (df['sma24'].shift(-24) > df['sma24']).astype(int)
df['target'] = ((df["sma24"].shift(-13) - df["sma24"]) > 0).astype(int)
df['target'] = ((df["sma24"].shift(-13) - df["sma24"]) > 100).astype(int)
df['target'] = df['target'].fillna(0).astype(int)
# Corrélations triées par importance avec une colonne cible
@@ -1545,11 +1662,11 @@ class FrictradeLearning(IStrategy):
# )
local_model = XGBClassifier(
n_estimators=300, #trial.suggest_int("n_estimators", 300, 500),
max_depth=trial.suggest_int("max_depth", 1, 3),
learning_rate=0.01, #trial.suggest_float("learning_rate", 0.005, 0.3, log=True),
subsample=0.7, #trial.suggest_float("subsample", 0.6, 1.0),
colsample_bytree=0.8, #trial.suggest_float("colsample_bytree", 0.6, 1.0),
n_estimators=trial.suggest_int("n_estimators", 300, 500),
max_depth=trial.suggest_int("max_depth", 1, 6),
learning_rate=trial.suggest_float("learning_rate", 0.005, 0.3, log=True),
subsample=trial.suggest_float("subsample", 0.6, 1.0),
colsample_bytree=trial.suggest_float("colsample_bytree", 0.6, 1.0),
scale_pos_weight=1,
objective="binary:logistic",
eval_metric="logloss",

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 279 KiB

After

Width:  |  Height:  |  Size: 309 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 74 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 62 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 103 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 62 KiB