FrictradeLearning.py Stoploss auto et gestion steps de mises adjust

This commit is contained in:
Jérôme Delacotte
2025-12-14 20:20:26 +01:00
parent 639afa42f0
commit c82b3359fa
16 changed files with 471 additions and 257 deletions

View File

@@ -9,22 +9,27 @@
},
"trailing": {
"trailing_stop": false,
"trailing_stop_positive": 0.15,
"trailing_stop_positive": 0.2,
"trailing_stop_positive_offset": 1,
"trailing_only_offset_is_reached": true
},
"max_open_trades": {
"max_open_trades": 80
},
"buy": {
"hours_force": 11
"max_open_trades": 20
},
"protection": {
"allow_decrease_rate": 0.7,
"first_adjust_param": 0.01,
"max_steps": 35
"allow_decrease_rate": 0.4,
"first_adjust_param": 0.005,
"max_steps": 45
},
"buy": {
"hours_force": 44,
"indic_1h_force_buy": "sma5_deriv1_1h"
},
"sell": {
"offset_max": 18,
"offset_min": 17
}
},
"ft_stratparam_v": 1,
"export_time": "2025-12-09 07:22:51.929255+00:00"
"export_time": "2025-12-14 18:44:03.713386+00:00"
}

View File

@@ -5,77 +5,57 @@
# IMPORTANT: INSTALL TA BEFOUR RUN(pip install ta)
# freqtrade hyperopt --hyperopt-loss SharpeHyperOptLoss --spaces buy sell roi --strategy Zeus
# --- Do not remove these libs ---
from datetime import timedelta, datetime
from freqtrade.persistence import Trade
from freqtrade.strategy import (BooleanParameter, CategoricalParameter, DecimalParameter, stoploss_from_open,
IntParameter, IStrategy, merge_informative_pair, informative, stoploss_from_absolute)
import pandas as pd
import numpy as np
import os
import json
import csv
from pandas import DataFrame
from typing import Optional, Union, Tuple
import math
import inspect
import logging
from pathlib import Path
import os
# --------------------------------
from datetime import datetime
from datetime import timezone
from typing import Optional
import freqtrade.vendor.qtpylib.indicators as qtpylib
# Machine Learning
import joblib
import matplotlib.pyplot as plt
import mpmath as mp
import numpy as np
import optuna
import pandas as pd
import seaborn as sns
import shap
# Add your lib to import here test git
import ta
import talib.abstract as talib
import freqtrade.vendor.qtpylib.indicators as qtpylib
from datetime import timezone, timedelta
import mpmath as mp
# Machine Learning
from sklearn.ensemble import RandomForestClassifier,RandomForestRegressor
from sklearn.model_selection import train_test_split
from sklearn.metrics import r2_score, mean_absolute_error, mean_squared_error
from sklearn.metrics import accuracy_score
import joblib
import matplotlib.pyplot as plt
from freqtrade.persistence import Trade
from freqtrade.strategy import (CategoricalParameter, DecimalParameter, IntParameter, IStrategy, merge_informative_pair)
from optuna.visualization import plot_optimization_history
from optuna.visualization import plot_parallel_coordinate
from optuna.visualization import plot_param_importances
from optuna.visualization import plot_slice
from pandas import DataFrame
from sklearn.calibration import CalibratedClassifierCV
from sklearn.feature_selection import SelectFromModel
from sklearn.feature_selection import VarianceThreshold
from sklearn.inspection import PartialDependenceDisplay
from sklearn.inspection import permutation_importance
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import brier_score_loss, roc_auc_score
from sklearn.metrics import (
classification_report,
confusion_matrix,
accuracy_score,
roc_auc_score,
roc_curve,
precision_score, recall_score, precision_recall_curve,
f1_score
precision_score, recall_score
)
from sklearn.tree import export_text
import inspect
from sklearn.feature_selection import mutual_info_classif
from sklearn.inspection import permutation_importance
from lightgbm import LGBMClassifier
from sklearn.calibration import CalibratedClassifierCV
from sklearn.feature_selection import SelectFromModel
from tabulate import tabulate
from sklearn.model_selection import GridSearchCV
from sklearn.feature_selection import VarianceThreshold
import seaborn as sns
from xgboost import XGBClassifier
import optuna
from optuna.visualization import plot_optimization_history
from optuna.visualization import plot_slice
from optuna.visualization import plot_param_importances
from optuna.visualization import plot_parallel_coordinate
import shap
from sklearn.inspection import PartialDependenceDisplay
from sklearn.model_selection import train_test_split
from sklearn.metrics import f1_score
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.tree import export_text
from xgboost import XGBClassifier
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.calibration import CalibratedClassifierCV
from sklearn.metrics import brier_score_loss, roc_auc_score
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
# --------------------------------
logger = logging.getLogger(__name__)
@@ -91,7 +71,7 @@ RESET = "\033[0m"
class FrictradeLearning(IStrategy):
startup_candle_count = 180
startup_candle_count = 200
train_model = None
model_indicators = []
DEFAULT_PARAMS = {
@@ -106,13 +86,21 @@ class FrictradeLearning(IStrategy):
"stoploss": -0.10,
"minimal_roi": {"0": 0.10}
}
indicators = {'sma24_deriv1', 'sma60_deriv1', 'sma5_deriv1_1h', 'sma12_deriv1_1h', 'sma24_deriv1_1h',
'sma60_deriv1_1h'}
indic_1h_force_buy = CategoricalParameter(indicators, default="sma60_deriv1", space='buy')
allow_decrease_rate = DecimalParameter(0.1, 0.8, decimals=1, default=0.4, space='protection')
first_adjust_param = DecimalParameter(0.001, 0.01, decimals=3, default=0.003, space='protection')
allow_decrease_rate = DecimalParameter(0.1, 0.8, decimals=1, default=0.4, space='protection', optimize=False,
load=False)
first_adjust_param = DecimalParameter(0.001, 0.01, decimals=3, default=0.005, space='protection', optimize=False,
load=False)
max_steps = IntParameter(10, 50, default=40, space='protection', optimize=True, load=True)
hours_force = IntParameter(1, 48, default=24, space='buy', optimize=True, load=True)
offset_min = IntParameter(1, 48, default=24, space='sell', optimize=True, load=True)
offset_max = IntParameter(1, 48, default=24, space='sell', optimize=True, load=True)
# ROI table:
minimal_roi = {
"0": 10
@@ -157,11 +145,13 @@ class FrictradeLearning(IStrategy):
'stop': False,
'max_profit': 0,
'first_amount': 0,
'first_price': 0,
'total_amount': 0,
'has_gain': 0,
'force_sell': False,
'force_buy': False,
'last_ath': 0,
'mises': {},
'dca_thresholds': {}
}
for pair in ["BTC/USDC", "ETH/USDC", "DOGE/USDC", "XRP/USDC", "SOL/USDC",
@@ -178,11 +168,30 @@ class FrictradeLearning(IStrategy):
{"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": "2024-03-14", "price_usd": 73816.00,
"note": "nouveau pic début 2024 (source presse, valeur indicative)"},
{"date": "2024-11-12", "price_usd": 90000.00, "note": ""},
{"date": "2024-12-17", "price_usd": 108363.00, "note": ""},
{"date": "2025-07-11", "price_usd": 118755.00, "note": "pic juillet 2025 (valeur rapportée par la presse)"},
{"date": "2025-08-13", "price_usd": 123748.00, "note": ""},
{"date": "2025-10-06", "price_usd": 126198.07,
"note": "pic oct. 2025 (source agrégée, à vérifier selon l'exchange)"}
]
def dynamic_trailing_offset(self, price, ath, nb_entries, max_dca=5):
dd_ath = (ath - price) / ath
dd_ath = max(0.0, min(dd_ath, 0.5))
dca_risk = min(nb_entries / max_dca, 1.0)
breathing_score = 0.7 * dd_ath + 0.3 * (1 - dca_risk)
breathing_score = min(max(breathing_score, 0.0), 1.0)
OFFSET_MIN = self.offset_min.value
OFFSET_MAX = self.offset_min.value + self.offset_max.value
return OFFSET_MIN + breathing_score * (OFFSET_MAX - OFFSET_MIN)
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:
@@ -195,9 +204,9 @@ class FrictradeLearning(IStrategy):
last_candle_2 = dataframe.iloc[-2].squeeze()
last_candle_3 = dataframe.iloc[-3].squeeze()
condition = True #(last_candle[f"{indic_5m}_deriv1"] >= indic_deriv1_5m) and (last_candle[f"{indic_5m}_deriv2"] >= indic_deriv2_5m)
condition = True # (last_candle[f"{indic_5m}_deriv1"] >= indic_deriv1_5m) and (last_candle[f"{indic_5m}_deriv2"] >= indic_deriv2_5m)
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')
if allow_to_buy:
self.trades = list()
@@ -235,13 +244,8 @@ 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)]
def progressive_parts(total, n, first):
print('In part')
def progressive_parts(self, total, n, first):
# print('In part')
# conditions impossibles → on évite le solveur
if total <= 0 or first <= 0 or n <= 1:
return [0] * n
@@ -257,21 +261,31 @@ class FrictradeLearning(IStrategy):
parts = [round(first * (r ** k), 4) for k in range(n)]
return parts
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)]
# r, parts = progressive_parts(0.4, 40, 0.004)
# print("r =", r)
# print(parts)
if self.pairs[pair]['last_ath'] == 0 :
ath = max(last_candle['mid'], self.get_last_ath_before_candle(last_candle))
val = self.pairs[pair]['first_price'] if self.pairs[pair]['first_price'] > 0 else last_candle['mid']
if self.pairs[pair]['last_ath'] == 0:
ath = max(val, self.get_last_ath_before_candle(last_candle['date']))
self.pairs[pair]['last_ath'] = ath
steps = self.approx_value(last_candle['mid'], self.pairs[pair]['last_ath'])
self.pairs[pair]['dca_thresholds'] = progressive_parts(
(last_candle['mid'] - (self.pairs[pair]['last_ath'] * (1 - self.allow_decrease_rate.value))) / last_candle['mid'],
ath = self.pairs[pair]['last_ath']
steps = self.calculateNumberOfSteps(val, ath, max_steps=self.max_steps.value)
self.pairs[pair]['dca_thresholds'] = self.progressive_parts(
(val - (ath * (1 - self.allow_decrease_rate.value))) / val,
steps, self.first_adjust_param.value)
print(f"val={last_candle['mid']} lim={self.pairs[pair]['last_ath'] * (1 - self.allow_decrease_rate.value)}"
f"steps={steps} "
f"pct={(round(last_candle['mid'] - (self.pairs[pair]['last_ath'] * (1 - self.allow_decrease_rate.value))) / last_candle['mid'], 4)}")
print(f"val={val} lim={self.pairs[pair]['last_ath'] * (1 - self.allow_decrease_rate.value)}"
f" steps={steps}"
f" pct={round((val - (self.pairs[pair]['last_ath'] * (1 - self.allow_decrease_rate.value))) / val, 4)}")
print(self.pairs[pair]['dca_thresholds'])
def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount: float, rate: float,
@@ -283,9 +297,10 @@ class FrictradeLearning(IStrategy):
last_candle = dataframe.iloc[-1].squeeze()
minutes = int(round((current_time - trade.open_date_utc).seconds / 60, 0))
profit =trade.calc_profit(rate)
profit = trade.calc_profit(rate)
force = self.pairs[pair]['force_sell']
allow_to_sell = minutes > 30 and (last_candle['hapercent'] < 0 ) or force or (exit_reason == 'force_exit') or (exit_reason == 'stop_loss')
# and (last_candle['hapercent'] < 0 )
allow_to_sell = True # (last_candle['hapercent'] < 0 ) or force or (exit_reason == 'force_exit') or (exit_reason == 'stop_loss')
if allow_to_sell:
self.trades = list()
@@ -306,6 +321,7 @@ class FrictradeLearning(IStrategy):
dispo=dispo,
profit=round(profit, 2)
)
self.pairs[pair]['first_amount'] = 0
self.pairs[pair]['max_profit'] = 0
self.pairs[pair]['force_sell'] = False
self.pairs[pair]['has_gain'] = 0
@@ -316,8 +332,9 @@ class FrictradeLearning(IStrategy):
self.pairs[pair]['last_buy'] = 0
self.pairs[pair]['last_date'] = current_time
self.pairs[pair]['current_trade'] = None
# else:
# self.printLog(f"{current_time} SELL triggered for {pair} ({exit_reason} profit={profit} minutes={minutes} percent={last_candle['hapercent']}) but condition blocked")
else:
self.printLog(
f"{current_time} SELL triggered for {pair} ({exit_reason} profit={profit} minutes={minutes} percent={last_candle['hapercent']}) but condition blocked")
return (allow_to_sell) | (exit_reason == 'force_exit') | (exit_reason == 'stop_loss')
# def custom_exit(self, pair: str, trade: Trade, current_time, current_rate, current_profit, **kwargs):
@@ -387,6 +404,7 @@ class FrictradeLearning(IStrategy):
def getLastLost(self, last_candle, pair):
last_lost = round((last_candle['close'] - self.pairs[pair]['max_touch']) / self.pairs[pair]['max_touch'], 3)
return last_lost
def getPctFirstBuy(self, pair, last_candle):
return round((last_candle['close'] - self.pairs[pair]['first_buy']) / self.pairs[pair]['first_buy'], 3)
@@ -400,7 +418,8 @@ class FrictradeLearning(IStrategy):
lim = 0.005
pct = 0.001
pct_to_max = lim + pct * self.pairs[pair]['count_of_buys']
expected_profit = lim * self.pairs[pair]['total_amount'] # min(3 * lim, max(lim, pct_to_max)) # 0.004 + 0.002 * self.pairs[pair]['count_of_buys'] #min(0.01, first_max)
expected_profit = lim * self.pairs[pair][
'total_amount'] # min(3 * lim, max(lim, pct_to_max)) # 0.004 + 0.002 * self.pairs[pair]['count_of_buys'] #min(0.01, first_max)
self.pairs[pair]['expected_profit'] = expected_profit
@@ -415,7 +434,8 @@ 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}|{'rsi_1h':>6}|{'rsi_1d':>6}|{'mlprob':>6}" #|Distmax|s201d|s5_1d|s5_2d|s51h|s52h|smt1h|smt2h|tdc1d|tdc1h"
f"{'rsi':>6}|{'rsi_1h':>6}|{'rsi_1d':>6}|{'cf_1h':>6}|{'cf_1d':>6}"
# |Distmax|s201d|s5_1d|s5_2d|s51h|s52h|smt1h|smt2h|tdc1d|tdc1h"
)
self.printLineLog()
df = pd.DataFrame.from_dict(self.pairs, orient='index')
@@ -467,7 +487,7 @@ class FrictradeLearning(IStrategy):
f"| {last_max or '-':>7} | {last_min or '-':>7} |{total_counts or '-':>5}|{stake or '-':>7}"
f"{round(last_candle['max_rsi_24'], 1) or '-':>6}|{round(last_candle['rsi_1h'], 1) or '-':>6}|{round(last_candle['rsi_1d'], 1) or '-':>6}|"
# f"{round(last_candle['rtp_1h'] * 100, 0) or '-' :>6}|{round(last_candle['rtp_1d'] * 100, 0) or '-' :>6}|"
f"{round(last_candle['ml_prob'], 1) or '-':>6}|"
# f"{round(last_candle['confidence_index_1d'], 3) or '-':>6}|{round(last_candle['confidence_index_1h'], 3) or '-':>6}|"
)
def printLineLog(self):
@@ -498,7 +518,8 @@ class FrictradeLearning(IStrategy):
dataframe['hapercent'] = (dataframe['haclose'] - dataframe['haopen']) / dataframe['haclose']
dataframe['mid'] = dataframe['open'] + (dataframe['close'] - dataframe['open']) / 2
dataframe['sma5'] = dataframe['mid'].ewm(span=5, adjust=False).mean() #dataframe["mid"].rolling(window=5).mean()
dataframe['sma5'] = dataframe['mid'].ewm(span=5,
adjust=False).mean() # dataframe["mid"].rolling(window=5).mean()
dataframe['sma5_deriv1'] = 1000 * (dataframe['sma5'] - dataframe['sma5'].shift(1)) / dataframe['sma5'].shift(1)
dataframe['sma12'] = dataframe['mid'].ewm(span=12, adjust=False).mean()
@@ -506,10 +527,12 @@ class FrictradeLearning(IStrategy):
'sma12'].shift(1)
dataframe['sma24'] = dataframe['mid'].ewm(span=24, adjust=False).mean()
dataframe['sma24_deriv1'] = 1000 * (dataframe['sma24'] - dataframe['sma24'].shift(1)) / dataframe['sma24'].shift(1)
dataframe['sma24_deriv1'] = 1000 * (dataframe['sma24'] - dataframe['sma24'].shift(1)) / dataframe[
'sma24'].shift(1)
dataframe['sma60'] = dataframe['mid'].ewm(span=60, adjust=False).mean()
dataframe['sma60_deriv1'] = 1000 * (dataframe['sma60'] - dataframe['sma60'].shift(1)) / dataframe['sma60'].shift(1)
dataframe['sma60_deriv1'] = 1000 * (dataframe['sma60'] - dataframe['sma60'].shift(1)) / dataframe[
'sma60'].shift(1)
# dataframe[f"sma5_inv"] = (dataframe[f"sma5"].shift(2) >= dataframe[f"sma5"].shift(1)) \
# & (dataframe[f"sma5"].shift(1) <= dataframe[f"sma5"])
@@ -546,7 +569,7 @@ class FrictradeLearning(IStrategy):
dataframe['max5'] = talib.MAX(dataframe['mid'], timeperiod=5)
dataframe['min180'] = talib.MIN(dataframe['mid'], timeperiod=180)
dataframe['max180'] = talib.MAX(dataframe['mid'], timeperiod=180)
dataframe['pct180'] = ((dataframe["mid"] - dataframe['min180'] ) / (dataframe['max180'] - dataframe['min180'] ))
dataframe['pct180'] = ((dataframe["mid"] - dataframe['min180']) / (dataframe['max180'] - dataframe['min180']))
dataframe = self.rsi_trend_probability(dataframe, short=60, long=360)
# ################### INFORMATIVE 1h
@@ -564,16 +587,30 @@ class FrictradeLearning(IStrategy):
informative['macdhist'] = macdhist
informative['rsi'] = talib.RSI(informative['mid'], timeperiod=14)
informative['sma5'] = informative['mid'].ewm(span=5, adjust=False).mean()
informative['sma5_deriv1'] = 1000 * (informative['sma5'] - informative['sma5'].shift(1)) / informative[
'sma5'].shift(1)
informative['sma12'] = informative['mid'].ewm(span=12, adjust=False).mean()
informative['sma12_deriv1'] = 1000 * (informative['sma12'] - informative['sma12'].shift(1)) / informative[
'sma12'].shift(1)
informative['sma24'] = informative['mid'].ewm(span=24, adjust=False).mean()
informative['sma24_deriv1'] = 1000 * (informative['sma24'] - informative['sma24'].shift(1)) / informative['sma24'].shift(1)
informative['sma24_deriv1'] = 1000 * (informative['sma24'] - informative['sma24'].shift(1)) / informative[
'sma24'].shift(1)
informative['sma60'] = informative['mid'].ewm(span=60, adjust=False).mean()
informative['sma60_deriv1'] = 1000 * (informative['sma60'] - informative['sma60'].shift(1)) / informative['sma60'].shift(1)
informative['sma60_deriv1'] = 1000 * (informative['sma60'] - informative['sma60'].shift(1)) / informative[
'sma60'].shift(1)
informative['rsi'] = talib.RSI(informative['mid'], timeperiod=14)
self.calculeDerivees(informative, 'rsi', ema_period=12)
# informative = self.rsi_trend_probability(informative)
probas = self.calculModelInformative(informative)
# probas = self.calculModelInformative(informative)
# self.calculateConfiance(informative)
# informative = self.populate1hIndicators(df=informative, metadata=metadata)
# informative = self.calculateRegression(informative, 'mid', lookback=15)
@@ -585,6 +622,8 @@ class FrictradeLearning(IStrategy):
informative['rsi'] = talib.RSI(informative['mid'], timeperiod=5)
# informative = self.rsi_trend_probability(informative)
# informative = self.calculateRegression(informative, 'mid', lookback=15)
# self.calculateConfiance(informative)
dataframe = merge_informative_pair(dataframe, informative, '1m', '1d', ffill=True)
dataframe['last_price'] = dataframe['close']
@@ -599,8 +638,8 @@ class FrictradeLearning(IStrategy):
filled_buys = trade.select_filled_orders('buy')
count = 0
amount = 0
min_price = 111111111111110;
max_price = 0;
min_price = 111111111111110
max_price = 0
for buy in filled_buys:
if count == 0:
min_price = min(min_price, buy.price)
@@ -616,7 +655,7 @@ class FrictradeLearning(IStrategy):
self.pairs[pair]['last_buy'] = buy.price
count = count + 1
amount += buy.price * buy.filled
count_buys = count
self.pairs[pair]['count_of_buys'] = count
self.pairs[pair]['total_amount'] = amount
dataframe['absolute_min'] = dataframe['mid'].rolling(1440, min_periods=1).min()
@@ -845,11 +884,55 @@ class FrictradeLearning(IStrategy):
# 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.value))) / val}")
# dca = split_ratio_one_third((val - (126000 * (1 - self.allow_decrease_rate.value))) / 126000, steps)
# print(dca)
allow_decrease_rate = 0.3
# for val in range(70000, 140000, 10000):
# ath = 126000
#
# steps = self.calculateNumberOfSteps(val, ath, max_steps=40)
# self.printLog(f"allow_decrease_rate={self.allow_decrease_rate.value} val={val} steps={steps} pct={round((val - (ath * (1 - allow_decrease_rate))) / val, 4)}")
# # dca = split_ratio_one_third((val - (ath * (1 - self.allow_decrease_rate.value))) / ath, steps)
# # self.printLog(dca)
# dca_thresholds = self.progressive_parts(
# (val - (ath * (1 - self.allow_decrease_rate.value))) / val,
# steps, self.first_adjust_param.value)
# print(f"val={val} lim={ath * (1 - self.allow_decrease_rate.value)}"
# f"steps={steps} "
# f"pct={(round(val - (ath * (1 - self.allow_decrease_rate.value))) / val, 4)}")
# print(dca_thresholds)
ath = 126000
last_candle = dataframe.iloc[-1].squeeze()
val = last_candle['first_price']
# steps = self.calculateNumberOfSteps(val, ath, max_steps=40)
# self.printLog(
# f"allow_decrease_rate={self.allow_decrease_rate.value} val={val} steps={steps} pct={round((val - (ath * (1 - allow_decrease_rate))) / val, 4)}")
# dca_thresholds = self.progressive_parts((val - (ath * (1 - self.allow_decrease_rate.value))) / val, steps, self.first_adjust_param.value)
# print(f"val={val} lim={ath * (1 - self.allow_decrease_rate.value)}"
# f"steps={steps} "
# f"pct={(round(val - (ath * (1 - self.allow_decrease_rate.value))) / val, 4)}")
# print(dca_thresholds)
if self.dp:
if self.dp.runmode.value in ('live', 'dry_run'):
full, mises, steps = self.calculateMises(last_candle, pair)
# 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:
print(self.pairs[pair]['dca_thresholds'])
count = 0
pct = 0
dataframe = dataframe.copy()
for dca in self.pairs[pair]['dca_thresholds']:
stake = mises[count]
pct += dca
offset = self.dynamic_trailing_offset(price=val, ath=ath, nb_entries=count)
if (count == self.pairs[pair]['count_of_buys']):
print(f"next_buy={val * (1 - pct)} count={count} pct={round(pct, 4)}")
dataframe[f"next_buy"] = val * (1 - pct)
count += 1
print(
f"stake={stake} count={count} pct={round(pct, 4)} offset={offset} next_buy={val * (1 - pct)}")
return dataframe
@@ -910,9 +993,9 @@ class FrictradeLearning(IStrategy):
(dataframe["ml_prob"].shift(1) < dataframe["ml_prob"])
& (dataframe['sma24_deriv1'] > 0)
& (dataframe['sma12_deriv1'] > 0)
& (dataframe['open'] < dataframe['max180'] * 0.997),
# & (dataframe['min180'].shift(3) == dataframe['min180']),
['enter_long', 'enter_tag']
# & (dataframe['open'] < dataframe['max180'] * 0.997)
# & (dataframe['min180'].shift(3) == dataframe['min180'])
, ['enter_long', 'enter_tag']
] = (1, f"future")
dataframe['test'] = np.where(dataframe['enter_long'] == 1, dataframe['close'] * 1.003, np.nan)
@@ -1029,50 +1112,40 @@ class FrictradeLearning(IStrategy):
#
# return adjusted_stake_amount
def approx_value(self, x, X_max):
X_min = X_max * (1 - self.allow_decrease_rate.value) # 126198 * 0.4 = 75718,8
def calculateNumberOfSteps(self, current, ath, max_steps=0):
if (max_steps == 0):
max_steps = self.max_steps.value
X_min = ath * (1 - self.allow_decrease_rate.value) # 126198 * 0.4 = 75718,8
Y_min = 1
Y_max = self.max_steps.value
a = (Y_max - Y_min) / (X_max - X_min) # 39 ÷ (126198 126198×0,6) = 0,000772595
Y_max = max_steps
a = (Y_max - Y_min) / (ath - 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
y = a * current + 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']
mises = self.pairs[pair]['mises']
count = self.pairs[pair]['count_of_buys'] - self.pairs[pair]['has_gain']
return mises[count] if count < len(mises) else self.pairs[pair]['first_amount']
ath = max(self.pairs[pair]['last_max'], self.get_last_ath_before_candle(last_candle))
full, mises, steps = self.calculateMises(last_candle, pair)
base_stake = mises[self.pairs[pair]['count_of_buys']] if self.pairs[pair]['count_of_buys'] < len(
mises) else full / (steps * 2)
return base_stake
def calculateMises(self, last_candle, pair):
ath = max(self.pairs[pair]['last_max'], self.get_last_ath_before_candle(last_candle['date']))
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))
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"]
high180 = last_candle["max180"]
mult = 1 - ((last_candle["mid"] - low180) / (high180 - low180))
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 = base_stake # exemple fraction du portefeuille; adapte selon ton code
# new stake proportionnel à mult
new_stake = base_size #* mult
return new_stake
steps = self.calculateNumberOfSteps(last_candle['mid'], ath, max_steps=self.max_steps.value)
mises = self.progressive_parts(full, steps, full / (steps * 2))
print(f"ath={ath} full={full} steps={steps} mises={mises} ")
self.pairs[pair]['mises'] = mises
return full, mises, steps
def adjust_trade_position(self, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, min_stake: float,
@@ -1083,6 +1156,11 @@ class FrictradeLearning(IStrategy):
return None
dataframe, _ = self.dp.get_analyzed_dataframe(trade.pair, self.timeframe)
if (len(dataframe) < 1):
# self.printLog("skip dataframe")
return None
last_candle = dataframe.iloc[-1].squeeze()
# before_last_candle = dataframe.iloc[-2].squeeze()
# prépare les données
@@ -1099,7 +1177,7 @@ class FrictradeLearning(IStrategy):
# open_date = trade.open_date.astimezone(timezone.utc)
# days_since_open = (current_time_utc - open_date).days
pair = trade.pair
profit = trade.calc_profit(current_rate) #round(current_profit * trade.stake_amount, 1)
profit = trade.calc_profit(current_rate) # round(current_profit * trade.stake_amount, 1)
# last_lost = self.getLastLost(last_candle, pair)
pct_first = 0
@@ -1109,32 +1187,32 @@ class FrictradeLearning(IStrategy):
# if self.pairs[pair]['first_buy']:
# pct_first = self.getPctFirstBuy(pair, last_candle)
if profit > - self.pairs[pair]['first_amount'] \
and self.wallets.get_available_stake_amount() < self.pairs[pair]['first_amount'] \
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 profit > - self.pairs[pair]['first_amount'] \
# and self.wallets.get_available_stake_amount() < self.pairs[pair]['first_amount'] \
# 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="🟥 Stoploss",
# 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
@@ -1145,7 +1223,7 @@ class FrictradeLearning(IStrategy):
return None
# Dernier prix d'achat réel (pas le prix moyen)
last_fill_price = self.pairs[trade.pair]['last_buy'] #trade.open_rate # remplacé juste après ↓
last_fill_price = self.pairs[trade.pair]['last_buy'] # trade.open_rate # remplacé juste après ↓
# if len(trade.orders) > 0:
# # On cherche le dernier BUY exécuté
@@ -1155,7 +1233,7 @@ class FrictradeLearning(IStrategy):
# baisse relative
# if minutes % 60 == 0:
# ath = max(self.pairs[pair]['last_max'], self.get_last_ath_before_candle(last_candle))
# 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']
@@ -1166,9 +1244,10 @@ class FrictradeLearning(IStrategy):
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)]
# print(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)}")
# 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)}")
decline = (last_fill_price - current_rate) / last_fill_price
increase = - decline
@@ -1178,10 +1257,9 @@ class FrictradeLearning(IStrategy):
# 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))
# 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
@@ -1208,7 +1286,7 @@ class FrictradeLearning(IStrategy):
# return None
# FIN ########################## ALGO ATH
force = hours > self.hours_force.value and last_candle['sma60_deriv1_1h'] > 0
force = False #hours > self.hours_force.value and last_candle[self.indic_1h_force_buy.value] > 0
condition = last_candle['percent'] > 0 and last_candle['sma24_deriv1'] > 0 \
and last_candle['close'] < self.pairs[pair]['first_buy']
@@ -1219,11 +1297,12 @@ class FrictradeLearning(IStrategy):
self.pairs[pair]['previous_profit'] = profit
return None
stake_amount = min(self.wallets.get_available_stake_amount(), self.adjust_stake_amount(pair, last_candle))
stake_amount = min(self.wallets.get_available_stake_amount(),
self.adjust_stake_amount(pair, last_candle))
if force:
stake_amount = stake_amount / 4
# print(f"profit={profit} previous={self.pairs[pair]['previous_profit']} count_of_buys={trade.nr_of_successful_entries}")
# if force:
# stake_amount = stake_amount / 2
# self.printLog(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
trade_type = "Loss " + (last_candle['enter_tag'] if last_candle['enter_long'] == 1 else '')
@@ -1260,11 +1339,14 @@ 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)\
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:
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)))
stake_amount = max(10, min(self.wallets.get_available_stake_amount(),
self.adjust_stake_amount(pair, last_candle)))
if stake_amount > 0:
self.pairs[pair]['has_gain'] += 1
@@ -1278,7 +1360,7 @@ class FrictradeLearning(IStrategy):
dispo=dispo,
pair=trade.pair,
rate=current_rate,
trade_type='Gain ' + str(round(dca_threshold, 4)),
trade_type='Gain ' + str(round(increase, 4)),
profit=round(profit, 1),
buys=trade.nr_of_successful_entries + 1,
stake=round(stake_amount, 2)
@@ -1304,7 +1386,7 @@ class FrictradeLearning(IStrategy):
# before_last_candle_12 = dataframe.iloc[-13].squeeze()
#
# expected_profit = self.expectedProfit(pair, last_candle)
# # print(f"current_time={current_time} current_profit={current_profit} expected_profit={expected_profit}")
# # self.printLog(f"current_time={current_time} current_profit={current_profit} expected_profit={expected_profit}")
#
# # ----- 1) Charger les variables de trailing pour ce trade -----
# max_price = self.pairs[pair]['max_touch']
@@ -1315,7 +1397,7 @@ class FrictradeLearning(IStrategy):
count_of_buys = trade.nr_of_successful_entries
profit = trade.calc_profit(current_rate) #round(current_profit * trade.stake_amount, 1)
profit = trade.calc_profit(current_rate) # round(current_profit * trade.stake_amount, 1)
if current_profit > 0:
self.pairs[pair]['max_profit'] = max(self.pairs[pair]['max_profit'], profit)
@@ -1325,8 +1407,7 @@ class FrictradeLearning(IStrategy):
max_profit = self.pairs[pair]['max_profit']
# if current_profit > 0:
# print(f"profit={profit} max_profit={max_profit} current_profit={current_profit}")
# self.printLog(f"profit={profit} max_profit={max_profit} current_profit={current_profit}")
# baisse = 0
# if profit > 0:
@@ -1351,6 +1432,10 @@ 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(price=current_rate,
ath=self.pairs[pair]['last_ath'],
nb_entries=count_of_buys)
# max_ = last_candle['max180']
# min_ = last_candle['min180']
# mid = last_candle['mid']
@@ -1393,8 +1478,8 @@ class FrictradeLearning(IStrategy):
if last_candle['rsi'] > 90:
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'] > last_candle['sma24']: # 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):
@@ -1402,27 +1487,28 @@ class FrictradeLearning(IStrategy):
# if (minutes > 1440 and last_candle['sma60_deriv1'] > 0) :
# return None
# ----- 4) OFFSET : faut-il attendre de dépasser trailing_stop_positive_offset ? -----
if current_trailing_only_offset_is_reached and max_profit > current_trailing_stop_positive_offset:
# Max profit pas atteint ET perte < 2 * current_trailing_stop_positive
if profit > max_profit * (1 - current_trailing_stop_positive): #2 * current_trailing_stop_positive:
print(f"{current_time} trailing non atteint trailing_stop={round(trailing_stop,4)} profit={round(profit, 4)} max={round(max_profit, 4)} "
f"{min(2, max_profit * (1 - current_trailing_stop_positive))}")
if profit > max_profit * (1 - current_trailing_stop_positive): # 2 * current_trailing_stop_positive:
print(
f"{current_time} trailing non atteint trailing_stop={round(trailing_stop, 4)} profit={round(profit, 4)} "
f"max={round(max_profit, 4)} offset={current_trailing_stop_positive_offset}")
return None # ne pas activer le trailing encore
else:
print(f"{current_time} trailing atteint trailing_stop={round(trailing_stop,4)} profit={round(profit, 4)} max={round(max_profit, 4)} "
f"{min(2,max_profit * (1 - current_trailing_stop_positive))}")
print(
f"{current_time} trailing atteint trailing_stop={round(trailing_stop, 4)} profit={round(profit, 4)} "
f"max={round(max_profit, 4)} offset={current_trailing_stop_positive_offset}")
else:
return None
# Sinon : trailing actif dès le début
# ----- 6) Condition de vente -----
if 0 < profit <= trailing_stop and last_candle['mid'] < last_candle['sma5']:
if 0 < profit <= trailing_stop: # and last_candle['mid'] < last_candle['sma5']: # and profit > current_trailing_stop_positive_offset:
self.pairs[pair]['force_buy'] = True
print(
f"{current_time} Condition de vente trailing_stop={round(trailing_stop,4)} profit={round(profit, 4)} max={round(max_profit, 4)} "
f"{round(min(2, max_profit * (1 - current_trailing_stop_positive)), 4)}")
f"{current_time} Condition de vente trailing_stop={round(trailing_stop, 4)} profit={round(profit, 4)} max={round(max_profit, 4)} "
f"{round(min(2, max_profit * (1 - current_trailing_stop_positive)), 4)} offset={current_trailing_stop_positive_offset}")
return f"stop_{count_of_buys}_{self.pairs[pair]['has_gain']}"
return None
@@ -1499,7 +1585,6 @@ class FrictradeLearning(IStrategy):
#
# return df
def rsi_trend_probability(self, dataframe, short=6, long=12):
dataframe = dataframe.copy()
@@ -1523,11 +1608,11 @@ class FrictradeLearning(IStrategy):
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):
def get_last_ath_before_candle(self, date):
candle_date = self.to_utc_ts(last_candle['date']) # ou to_utc_ts(last_candle.name)
candle_date = self.to_utc_ts(date) # ou to_utc_ts(last_candle.name)
best = None
for a in self.btc_ath_history: #getattr(self, "btc_ath_history", []):
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]:
@@ -1539,7 +1624,7 @@ class FrictradeLearning(IStrategy):
pd.set_option('display.max_rows', None)
pd.set_option('display.max_columns', None)
pd.set_option("display.width", 200)
path=self.path #f"user_data/plots/{pair}/"
path = self.path # f"user_data/plots/{pair}/"
os.makedirs(path, exist_ok=True)
# # Étape 1 : sélectionner numériques
@@ -1717,6 +1802,7 @@ class FrictradeLearning(IStrategy):
best_f1 = max(f1_score(y_valid, (proba > t)) for t in thresholds)
return best_f1
study = optuna.create_study(direction="maximize")
study.optimize(objective, n_trials=20)
@@ -1801,7 +1887,6 @@ class FrictradeLearning(IStrategy):
fig = plot_parallel_coordinate(study)
fig.write_html(f"{self.path}/parallel_coordinates.html")
# 2⃣ Sélection des features AVANT calibration
sfm = SelectFromModel(self.train_model, threshold="median", prefit=True)
selected_features = X_train.columns[sfm.get_support()]
@@ -1897,7 +1982,6 @@ class FrictradeLearning(IStrategy):
joblib.dump(self.train_model, f"{self.path}/{pair}_rf_model.pkl")
print(f"✅ Modèle sauvegardé sous {pair}_rf_model.pkl")
# X = dataframe des features (après shift/rolling/indicators)
# y = target binaire ou décimale
# model = ton modèle entraîné (RandomForestClassifier ou Regressor)
@@ -2226,15 +2310,19 @@ class FrictradeLearning(IStrategy):
and not c.startswith('stop_buying')
and not c.startswith('target')
and not c.startswith('lvl')
and not c.startswith('sma5_deriv1_1h')
and not c.startswith('sma5_1h')
and not c.startswith('sma12_deriv1_1h')
and not c.startswith('sma12_1h')
and not c.startswith('confidence_index')
]
# Étape 3 : remplacer inf et NaN par 0
dataframe[usable_cols] = dataframe[usable_cols].replace([np.inf, -np.inf], 0).fillna(0)
print("Colonnes utilisables pour le modèle :")
print(usable_cols)
# print("Colonnes utilisables pour le modèle :")
# print(usable_cols)
# self.model_indicators = usable_cols
return usable_cols
def select_uncorrelated_features(self, df, target, top_n=20, corr_threshold=0.7):
"""
Sélectionne les features les plus corrélées avec target,
@@ -2295,11 +2383,12 @@ class FrictradeLearning(IStrategy):
factor1 = 100 * (ema_period / 5)
factor2 = 10 * (ema_period / 5)
dataframe[f"{name}{suffixe}_inv"] = (dataframe[f"{name}{suffixe}"].shift(2) >= dataframe[f"{name}{suffixe}"].shift(1)) \
dataframe[f"{name}{suffixe}_inv"] = (dataframe[f"{name}{suffixe}"].shift(2) >= dataframe[
f"{name}{suffixe}"].shift(1)) \
& (dataframe[f"{name}{suffixe}"].shift(1) <= dataframe[f"{name}{suffixe}"])
# --- Distance à la moyenne mobile ---
dataframe[f"{name}{suffixe}_dist"] = (dataframe['close'] - dataframe[f"{name}{suffixe}"]) / dataframe[f"{name}{suffixe}"]
dataframe[f"{name}{suffixe}_dist"] = (dataframe['close'] - dataframe[f"{name}{suffixe}"]) / dataframe[
f"{name}{suffixe}"]
# dérivée relative simple
dataframe[d1_col] = (dataframe[name] - dataframe[name].shift(1)) / dataframe[name].shift(1)
@@ -2380,6 +2469,44 @@ class FrictradeLearning(IStrategy):
return dataframe
def calculateConfiance(self, informative):
df = informative.copy()
# ATR normalisé
df['atr_norm'] = talib.ATR(df['high'], df['low'], df['close'], length=14) / df['close']
# SMA200 & pente
df['sma200'] = talib.SMA(df['close'], 200)
df['sma200_slope'] = df['sma200'].diff()
# drawdown
df['rolling_ath'] = df['close'].cummax()
df['drawdown'] = (df['close'] - df['rolling_ath']) / df['rolling_ath']
# volume spike
df['vol_spike'] = df['volume'] / df['volume'].rolling(20).mean()
# RSI courts/longs
df['rsi14'] = talib.RSI(df['close'], 14)
df['rsi60'] = talib.RSI(df['close'], 60)
# Scores normalisés
df['vol_score'] = 1 - np.clip(df['atr_norm'] / 0.05, 0, 1)
df['trend_score'] = 1 / (1 + np.exp(-df['sma200_slope'] * 150))
df['dd_score'] = 1 - np.clip(abs(df['drawdown']) / 0.3, 0, 1)
df['volpanic_score'] = 1 - np.clip(df['vol_spike'] / 3, 0, 1)
df['rsi_score'] = 1 / (1 + np.exp(-(df['rsi14'] - df['rsi60']) / 10))
# Indice final
informative['confidence_index'] = (
0.25 * df['vol_score'] +
0.25 * df['trend_score'] +
0.20 * df['dd_score'] +
0.15 * df['volpanic_score'] +
0.15 * df['rsi_score']
)
return informative
def calculModelInformative(self, informative):
# préparation
# print(df)
@@ -2415,7 +2542,6 @@ class FrictradeLearning(IStrategy):
# joindre probabilités au df (dernières lignes correspondantes)
return probas
def prune_features(self, model, dataframe, feature_columns, importance_threshold=0.01):
"""
Supprime les features dont l'importance est inférieure au seuil.
@@ -2444,6 +2570,6 @@ class FrictradeLearning(IStrategy):
dataframe_pruned = dataframe[kept_features].fillna(0)
print(f"⚡ Features conservées ({len(kept_features)} / {len(feature_columns)}): {kept_features}")
# print(f"⚡ Features conservées ({len(kept_features)} / {len(feature_columns)}): {kept_features}")
return dataframe_pruned, kept_features

Binary file not shown.

View File

@@ -1 +1 @@
0.2788135593220339
0.14152542372881355

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 24 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: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,32 @@
import numpy as np
import matplotlib.pyplot as plt
from lightgbm import LGBMRegressor
# === Données non linéaires ===
np.random.seed(0)
X = np.linspace(0, 10, 200).reshape(-1, 1)
y = np.sin(X).ravel() + np.random.normal(0, 0.1, X.shape[0]) # sinusoïde + bruit
# === Entraînement du modèle ===
model = LGBMRegressor(
n_estimators=300, # nombre darbres
learning_rate=0.05, # taux dapprentissage (plus petit = plus lisse)
max_depth=5 # profondeur des arbres (plus grand = plus complexe)
)
model.fit(X, y)
# === Prédiction ===
X_test = np.linspace(0, 10, 500).reshape(-1, 1)
y_pred = model.predict(X_test)
# === Visualisation ===
plt.figure(figsize=(10, 5))
plt.scatter(X, y, color="lightgray", label="Données réelles (sin + bruit)", s=20)
plt.plot(X_test, np.sin(X_test), color="green", linestyle="--", label="sin(x) réel")
plt.plot(X_test, y_pred, color="red", label="Prédiction LGBM")
plt.title("Approximation non linéaire avec LGBMRegressor")
plt.xlabel("x")
plt.ylabel("y")
plt.legend()
plt.grid(True)
plt.show()

View File

@@ -445,6 +445,46 @@ class Crash:
return selected_corr
def drawPredictions(self, df, indicators, y_proba, threshold=0.5):
"""
Trace simultanément plusieurs indicateurs et la prédiction du modèle.
Parameters
----------
df : pd.DataFrame
Dataframe avec les colonnes des indicateurs et la target.
indicators : list[str]
Liste des colonnes du dataframe à tracer.
y_proba : np.array
Probabilités prédites par le modèle (valeurs continues entre 0 et 1)
threshold : float
Seuil pour convertir probabilité en signal binaire
output_file : str
Fichier sur lequel sauvegarder le graphique
"""
plt.figure(figsize=(18, 6))
# Tracer les indicateurs
for col in indicators:
plt.plot(df.index, df[col], label=col, alpha=0.7)
# Tracer la prédiction du modèle (probabilité)
plt.plot(df.index, y_proba, label="Prediction prob.", color="black", linestyle="--")
# Optionnel : signal binaire (1 si prob > threshold)
y_signal = (y_proba > threshold).astype(int)
plt.scatter(df.index, y_signal, color='red', marker='o', label='Signal > threshold', s=20)
plt.title("Indicateurs + prédiction MLP")
plt.xlabel("Date")
plt.ylabel("Valeur / Probabilité")
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.savefig(f"{self.path}/indicators_vs_prediction.png")
plt.close()
def drawSequentialGraphs(self, model, history, X_train_scaled, X_valid_scaled, y_train,
y_valid, thresholds=None, best_threshold=None
):
@@ -556,6 +596,17 @@ class Crash:
# plt.savefig(f"{self.path}/permutation_importance.png")
# plt.close()
# Exemple : on choisit 3 indicateurs du dataframe
indicators = ['percent12']
# y_proba = model.predict(X_valid_scaled).ravel()
self.drawPredictions(
df=self.dataframe.iloc[-len(y_proba):], # sélectionner la période correspondant à X_valid
indicators=indicators,
y_proba=y_proba,
threshold=0.5
)
# =========================
# 7⃣ Sauvegarde seuil
# =========================
@@ -567,7 +618,7 @@ class Crash:
"best_f1": max(f1s)
}
def optimize_sequential(self, X_train, y_train, X_valid, y_valid, n_trials=20):
def optimize_sequential(self, X_train, X_valid, y_train, y_valid , n_trials=20):
def objective(trial):
tf.keras.backend.clear_session()
@@ -835,7 +886,7 @@ class Crash:
# df['target'] = df['target'].fillna(0).astype(int)
# label : crash si -n% dans les p heures
self.calculateTarget(df)
self.initTarget(df)
self.calculateCorrelation(df)
@@ -872,7 +923,7 @@ class Crash:
assert len(X_train) == len(y_train)
assert len(X_valid) == len(y_valid)
self.train_model, study = self.optimize_sequential(X_train, y_train, X_valid, y_valid, n_trials=50)
self.train_model, study = self.optimize_sequential(X_train, X_valid, y_train, y_valid, n_trials=50)
self.analyseStudy(study)
@@ -939,7 +990,7 @@ class Crash:
# plt.ylabel("Score")
# plt.show()
self.analyze_model(pair, self.train_model, X_train, X_valid, y_train, y_valid)
self.analyze_model(self.train_model, X_train, X_valid, y_train, y_valid)
def analyseImportances(self, selected_features, X_train, X_valid, y_valid):
# Feature importance
@@ -1137,7 +1188,7 @@ class Crash:
# FIN SHAP
def calculateTarget(self, df):
def initTarget(self, df):
future = df['mid'].shift(-12)
df['future_dd'] = (future - df['mid']) / df['mid']
df['target'] = (df['future_dd'] > 0.003).astype(int)
@@ -1214,7 +1265,7 @@ class Crash:
print("\n===== ✅ FIN DE LINSPECTION =====")
def analyze_model(self, pair, model, X_train, X_valid, y_train, y_valid):
def analyze_model(self, model, X_train, X_valid, y_train, y_valid):
"""
Analyse complète d'un modèle ML supervisé (classification binaire).
Affiche performances, importance des features, matrices, seuils, etc.
@@ -1398,7 +1449,7 @@ class Crash:
df = df.set_index('date')
# Optionnel : ne garder quune plage temporelle
df = df["2025-01-01":"2025-07-14"]
df = df["2025-01-01":"2025-02-01"]
df = df.reset_index('date')
# Supprimer NaN