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": {
"trailing_stop": false, "trailing_stop": false,
"trailing_stop_positive": 0.15, "trailing_stop_positive": 0.2,
"trailing_stop_positive_offset": 1, "trailing_stop_positive_offset": 1,
"trailing_only_offset_is_reached": true "trailing_only_offset_is_reached": true
}, },
"max_open_trades": { "max_open_trades": {
"max_open_trades": 80 "max_open_trades": 20
},
"buy": {
"hours_force": 11
}, },
"protection": { "protection": {
"allow_decrease_rate": 0.7, "allow_decrease_rate": 0.4,
"first_adjust_param": 0.01, "first_adjust_param": 0.005,
"max_steps": 35 "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, "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) # IMPORTANT: INSTALL TA BEFOUR RUN(pip install ta)
# freqtrade hyperopt --hyperopt-loss SharpeHyperOptLoss --spaces buy sell roi --strategy Zeus # freqtrade hyperopt --hyperopt-loss SharpeHyperOptLoss --spaces buy sell roi --strategy Zeus
# --- Do not remove these libs --- # --- Do not remove these libs ---
from datetime import timedelta, datetime import inspect
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 logging 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 # Add your lib to import here test git
import ta import ta
import talib.abstract as talib import talib.abstract as talib
import freqtrade.vendor.qtpylib.indicators as qtpylib from freqtrade.persistence import Trade
from datetime import timezone, timedelta from freqtrade.strategy import (CategoricalParameter, DecimalParameter, IntParameter, IStrategy, merge_informative_pair)
import mpmath as mp from optuna.visualization import plot_optimization_history
from optuna.visualization import plot_parallel_coordinate
# Machine Learning from optuna.visualization import plot_param_importances
from sklearn.ensemble import RandomForestClassifier,RandomForestRegressor from optuna.visualization import plot_slice
from sklearn.model_selection import train_test_split from pandas import DataFrame
from sklearn.metrics import r2_score, mean_absolute_error, mean_squared_error from sklearn.calibration import CalibratedClassifierCV
from sklearn.metrics import accuracy_score from sklearn.feature_selection import SelectFromModel
import joblib from sklearn.feature_selection import VarianceThreshold
import matplotlib.pyplot as plt 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 ( from sklearn.metrics import (
classification_report, classification_report,
confusion_matrix, confusion_matrix,
accuracy_score, accuracy_score,
roc_auc_score,
roc_curve, roc_curve,
precision_score, recall_score, precision_recall_curve, precision_score, recall_score
f1_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.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 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__) logger = logging.getLogger(__name__)
@@ -91,7 +71,7 @@ RESET = "\033[0m"
class FrictradeLearning(IStrategy): class FrictradeLearning(IStrategy):
startup_candle_count = 180 startup_candle_count = 200
train_model = None train_model = None
model_indicators = [] model_indicators = []
DEFAULT_PARAMS = { DEFAULT_PARAMS = {
@@ -106,13 +86,21 @@ class FrictradeLearning(IStrategy):
"stoploss": -0.10, "stoploss": -0.10,
"minimal_roi": {"0": 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') allow_decrease_rate = DecimalParameter(0.1, 0.8, decimals=1, default=0.4, space='protection', optimize=False,
first_adjust_param = DecimalParameter(0.001, 0.01, decimals=3, default=0.003, space='protection') 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) 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) 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: # ROI table:
minimal_roi = { minimal_roi = {
"0": 10 "0": 10
@@ -157,11 +145,13 @@ class FrictradeLearning(IStrategy):
'stop': False, 'stop': False,
'max_profit': 0, 'max_profit': 0,
'first_amount': 0, 'first_amount': 0,
'first_price': 0,
'total_amount': 0, 'total_amount': 0,
'has_gain': 0, 'has_gain': 0,
'force_sell': False, 'force_sell': False,
'force_buy': False, 'force_buy': False,
'last_ath': 0, 'last_ath': 0,
'mises': {},
'dca_thresholds': {} 'dca_thresholds': {}
} }
for pair in ["BTC/USDC", "ETH/USDC", "DOGE/USDC", "XRP/USDC", "SOL/USDC", 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": "2021-11-10", "price_usd": 68742.00, "note": "record novembre 2021 (institutional demand)"},
{"date": "2024-03-05", "price_usd": 69000.00, {"date": "2024-03-05", "price_usd": 69000.00,
"note": "nouveau pic début 2024 (source presse, valeur indicative)"}, "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-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, {"date": "2025-10-06", "price_usd": 126198.07,
"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, 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, 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:
@@ -195,9 +204,9 @@ class FrictradeLearning(IStrategy):
last_candle_2 = dataframe.iloc[-2].squeeze() last_candle_2 = dataframe.iloc[-2].squeeze()
last_candle_3 = dataframe.iloc[-3].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: if allow_to_buy:
self.trades = list() self.trades = list()
@@ -235,13 +244,8 @@ class FrictradeLearning(IStrategy):
return allow_to_buy return allow_to_buy
def calculateStepsDcaThresholds(self, last_candle, pair): def progressive_parts(self, total, n, first):
# def split_ratio_one_third(n, p): # print('In part')
# 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')
# conditions impossibles → on évite le solveur # conditions impossibles → on évite le solveur
if total <= 0 or first <= 0 or n <= 1: if total <= 0 or first <= 0 or n <= 1:
return [0] * n return [0] * n
@@ -257,21 +261,31 @@ class FrictradeLearning(IStrategy):
parts = [round(first * (r ** k), 4) for k in range(n)] parts = [round(first * (r ** k), 4) for k in range(n)]
return parts 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) # r, parts = progressive_parts(0.4, 40, 0.004)
# print("r =", r) # print("r =", r)
# print(parts) # print(parts)
if self.pairs[pair]['last_ath'] == 0 : val = self.pairs[pair]['first_price'] if self.pairs[pair]['first_price'] > 0 else last_candle['mid']
ath = max(last_candle['mid'], self.get_last_ath_before_candle(last_candle))
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 self.pairs[pair]['last_ath'] = ath
steps = self.approx_value(last_candle['mid'], self.pairs[pair]['last_ath']) ath = 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'], 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) steps, self.first_adjust_param.value)
print(f"val={last_candle['mid']} lim={self.pairs[pair]['last_ath'] * (1 - self.allow_decrease_rate.value)}" print(f"val={val} lim={self.pairs[pair]['last_ath'] * (1 - self.allow_decrease_rate.value)}"
f"steps={steps} " f" steps={steps}"
f"pct={(round(last_candle['mid'] - (self.pairs[pair]['last_ath'] * (1 - self.allow_decrease_rate.value))) / last_candle['mid'], 4)}") f" pct={round((val - (self.pairs[pair]['last_ath'] * (1 - self.allow_decrease_rate.value))) / val, 4)}")
print(self.pairs[pair]['dca_thresholds']) print(self.pairs[pair]['dca_thresholds'])
def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount: float, rate: float, 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() last_candle = dataframe.iloc[-1].squeeze()
minutes = int(round((current_time - trade.open_date_utc).seconds / 60, 0)) 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'] 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: if allow_to_sell:
self.trades = list() self.trades = list()
@@ -306,6 +321,7 @@ class FrictradeLearning(IStrategy):
dispo=dispo, dispo=dispo,
profit=round(profit, 2) profit=round(profit, 2)
) )
self.pairs[pair]['first_amount'] = 0
self.pairs[pair]['max_profit'] = 0 self.pairs[pair]['max_profit'] = 0
self.pairs[pair]['force_sell'] = False self.pairs[pair]['force_sell'] = False
self.pairs[pair]['has_gain'] = 0 self.pairs[pair]['has_gain'] = 0
@@ -316,8 +332,9 @@ class FrictradeLearning(IStrategy):
self.pairs[pair]['last_buy'] = 0 self.pairs[pair]['last_buy'] = 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
# else: else:
# self.printLog(f"{current_time} SELL triggered for {pair} ({exit_reason} profit={profit} minutes={minutes} percent={last_candle['hapercent']}) but condition blocked") 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') 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): # 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): def getLastLost(self, last_candle, pair):
last_lost = round((last_candle['close'] - self.pairs[pair]['max_touch']) / self.pairs[pair]['max_touch'], 3) last_lost = round((last_candle['close'] - self.pairs[pair]['max_touch']) / self.pairs[pair]['max_touch'], 3)
return last_lost return last_lost
def getPctFirstBuy(self, pair, last_candle): def getPctFirstBuy(self, pair, last_candle):
return round((last_candle['close'] - self.pairs[pair]['first_buy']) / self.pairs[pair]['first_buy'], 3) 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 lim = 0.005
pct = 0.001 pct = 0.001
pct_to_max = lim + pct * self.pairs[pair]['count_of_buys'] 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 self.pairs[pair]['expected_profit'] = expected_profit
@@ -415,7 +434,8 @@ class FrictradeLearning(IStrategy):
self.printLog( self.printLog(
f"| {'Date':<16} | {'Action':<10} |{'Pair':<5}| {'Trade Type':<18} |{'Rate':>8} | {'Dispo':>6} | {'Profit':>8} " f"| {'Date':<16} | {'Action':<10} |{'Pair':<5}| {'Trade Type':<18} |{'Rate':>8} | {'Dispo':>6} | {'Profit':>8} "
f"| {'Pct':>6} | {'max_touch':>11} | {'last_lost':>12} | {'last_max':>7}| {'last_min':>7}|{'Buys':>5}| {'Stake':>5} |" f"| {'Pct':>6} | {'max_touch':>11} | {'last_lost':>12} | {'last_max':>7}| {'last_min':>7}|{'Buys':>5}| {'Stake':>5} |"
f"{'rsi':>6}|{'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() self.printLineLog()
df = pd.DataFrame.from_dict(self.pairs, orient='index') 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"| {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['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['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): def printLineLog(self):
@@ -498,7 +518,8 @@ class FrictradeLearning(IStrategy):
dataframe['hapercent'] = (dataframe['haclose'] - dataframe['haopen']) / dataframe['haclose'] dataframe['hapercent'] = (dataframe['haclose'] - dataframe['haopen']) / dataframe['haclose']
dataframe['mid'] = dataframe['open'] + (dataframe['close'] - dataframe['open']) / 2 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['sma5_deriv1'] = 1000 * (dataframe['sma5'] - dataframe['sma5'].shift(1)) / dataframe['sma5'].shift(1)
dataframe['sma12'] = dataframe['mid'].ewm(span=12, adjust=False).mean() dataframe['sma12'] = dataframe['mid'].ewm(span=12, adjust=False).mean()
@@ -506,10 +527,12 @@ class FrictradeLearning(IStrategy):
'sma12'].shift(1) 'sma12'].shift(1)
dataframe['sma24'] = dataframe['mid'].ewm(span=24, adjust=False).mean() 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'] = 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_inv"] = (dataframe[f"sma5"].shift(2) >= dataframe[f"sma5"].shift(1)) \
# & (dataframe[f"sma5"].shift(1) <= dataframe[f"sma5"]) # & (dataframe[f"sma5"].shift(1) <= dataframe[f"sma5"])
@@ -546,7 +569,7 @@ class FrictradeLearning(IStrategy):
dataframe['max5'] = talib.MAX(dataframe['mid'], timeperiod=5) dataframe['max5'] = talib.MAX(dataframe['mid'], timeperiod=5)
dataframe['min180'] = talib.MIN(dataframe['mid'], timeperiod=180) dataframe['min180'] = talib.MIN(dataframe['mid'], timeperiod=180)
dataframe['max180'] = talib.MAX(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) dataframe = self.rsi_trend_probability(dataframe, short=60, long=360)
# ################### INFORMATIVE 1h # ################### INFORMATIVE 1h
@@ -564,16 +587,30 @@ class FrictradeLearning(IStrategy):
informative['macdhist'] = macdhist informative['macdhist'] = macdhist
informative['rsi'] = talib.RSI(informative['mid'], timeperiod=14) 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'] = 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'] = 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) informative['rsi'] = talib.RSI(informative['mid'], timeperiod=14)
self.calculeDerivees(informative, 'rsi', ema_period=12) self.calculeDerivees(informative, 'rsi', ema_period=12)
# informative = self.rsi_trend_probability(informative) # 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.populate1hIndicators(df=informative, metadata=metadata)
# informative = self.calculateRegression(informative, 'mid', lookback=15) # informative = self.calculateRegression(informative, 'mid', lookback=15)
@@ -585,6 +622,8 @@ class FrictradeLearning(IStrategy):
informative['rsi'] = talib.RSI(informative['mid'], timeperiod=5) informative['rsi'] = talib.RSI(informative['mid'], timeperiod=5)
# informative = self.rsi_trend_probability(informative) # informative = self.rsi_trend_probability(informative)
# informative = self.calculateRegression(informative, 'mid', lookback=15) # informative = self.calculateRegression(informative, 'mid', lookback=15)
# self.calculateConfiance(informative)
dataframe = merge_informative_pair(dataframe, informative, '1m', '1d', ffill=True) dataframe = merge_informative_pair(dataframe, informative, '1m', '1d', ffill=True)
dataframe['last_price'] = dataframe['close'] dataframe['last_price'] = dataframe['close']
@@ -599,8 +638,8 @@ class FrictradeLearning(IStrategy):
filled_buys = trade.select_filled_orders('buy') filled_buys = trade.select_filled_orders('buy')
count = 0 count = 0
amount = 0 amount = 0
min_price = 111111111111110; min_price = 111111111111110
max_price = 0; max_price = 0
for buy in filled_buys: for buy in filled_buys:
if count == 0: if count == 0:
min_price = min(min_price, buy.price) min_price = min(min_price, buy.price)
@@ -616,7 +655,7 @@ class FrictradeLearning(IStrategy):
self.pairs[pair]['last_buy'] = buy.price self.pairs[pair]['last_buy'] = buy.price
count = count + 1 count = count + 1
amount += buy.price * buy.filled amount += buy.price * buy.filled
count_buys = count self.pairs[pair]['count_of_buys'] = count
self.pairs[pair]['total_amount'] = amount self.pairs[pair]['total_amount'] = amount
dataframe['absolute_min'] = dataframe['mid'].rolling(1440, min_periods=1).min() 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 # d = n / (p * (p - 1)) # incrément
# return [round(a + i * d, 3) for i in range(p)] # return [round(a + i * d, 3) for i in range(p)]
# #
# for val in range(90000, 130000, 10000): allow_decrease_rate = 0.3
# steps = self.approx_value(val, 126000) # for val in range(70000, 140000, 10000):
# print(f"val={val} steps={steps} pct={(val - (126000 * (1 - self.allow_decrease_rate.value))) / val}") # ath = 126000
# dca = split_ratio_one_third((val - (126000 * (1 - self.allow_decrease_rate.value))) / 126000, steps) #
# print(dca) # 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 return dataframe
@@ -910,9 +993,9 @@ class FrictradeLearning(IStrategy):
(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['sma12_deriv1'] > 0)
& (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']
] = (1, f"future") ] = (1, f"future")
dataframe['test'] = np.where(dataframe['enter_long'] == 1, dataframe['close'] * 1.003, np.nan) 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 # return adjusted_stake_amount
def approx_value(self, x, X_max): def calculateNumberOfSteps(self, current, ath, max_steps=0):
X_min = X_max * (1 - self.allow_decrease_rate.value) # 126198 * 0.4 = 75718,8 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_min = 1
Y_max = self.max_steps.value Y_max = max_steps
a = (Y_max - Y_min) / (X_max - X_min) # 39 ÷ (126198 126198×0,6) = 0,000772595 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 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 return max(round(y), 1) # évite les valeurs négatives
def adjust_stake_amount(self, pair: str, last_candle: DataFrame): def adjust_stake_amount(self, pair: str, last_candle: DataFrame):
if self.pairs[pair]['first_amount'] > 0: 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 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() full = self.wallets.get_total_stake_amount()
steps = self.approx_value(last_candle['mid'], ath) steps = self.calculateNumberOfSteps(last_candle['mid'], ath, max_steps=self.max_steps.value)
base_stake = full / steps mises = self.progressive_parts(full, steps, full / (steps * 2))
print(f"ath={ath} full={full} steps={steps} mises={mises} ")
# base_stake = stake * (1 + (ath_dist / 40)) self.pairs[pair]['mises'] = mises
return full, mises, steps
# 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
def adjust_trade_position(self, trade: Trade, current_time: datetime, def adjust_trade_position(self, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, min_stake: float, current_rate: float, current_profit: float, min_stake: float,
@@ -1083,6 +1156,11 @@ class FrictradeLearning(IStrategy):
return None return None
dataframe, _ = self.dp.get_analyzed_dataframe(trade.pair, self.timeframe) 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() last_candle = dataframe.iloc[-1].squeeze()
# before_last_candle = dataframe.iloc[-2].squeeze() # before_last_candle = dataframe.iloc[-2].squeeze()
# prépare les données # prépare les données
@@ -1099,7 +1177,7 @@ class FrictradeLearning(IStrategy):
# open_date = trade.open_date.astimezone(timezone.utc) # open_date = trade.open_date.astimezone(timezone.utc)
# days_since_open = (current_time_utc - open_date).days # days_since_open = (current_time_utc - open_date).days
pair = trade.pair 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) # last_lost = self.getLastLost(last_candle, pair)
pct_first = 0 pct_first = 0
@@ -1109,32 +1187,32 @@ class FrictradeLearning(IStrategy):
# if self.pairs[pair]['first_buy']: # if self.pairs[pair]['first_buy']:
# pct_first = self.getPctFirstBuy(pair, last_candle) # pct_first = self.getPctFirstBuy(pair, last_candle)
if profit > - self.pairs[pair]['first_amount'] \ # if profit > - self.pairs[pair]['first_amount'] \
and self.wallets.get_available_stake_amount() < self.pairs[pair]['first_amount'] \ # and self.wallets.get_available_stake_amount() < self.pairs[pair]['first_amount'] \
and last_candle['sma24_deriv1_1h'] < 0: # and last_candle['sma24_deriv1_1h'] < 0:
stake_amount = trade.stake_amount # stake_amount = trade.stake_amount
self.pairs[pair]['previous_profit'] = profit # self.pairs[pair]['previous_profit'] = profit
trade_type = "Sell " + (last_candle['enter_tag'] if last_candle['enter_long'] == 1 else '') # trade_type = "Sell " + (last_candle['enter_tag'] if last_candle['enter_long'] == 1 else '')
self.pairs[trade.pair]['count_of_buys'] += 1 # self.pairs[trade.pair]['count_of_buys'] += 1
self.pairs[pair]['total_amount'] = stake_amount # self.pairs[pair]['total_amount'] = stake_amount
self.log_trade( # self.log_trade(
last_candle=last_candle, # last_candle=last_candle,
date=current_time, # date=current_time,
action="🟧 Sell +", # action="🟥 Stoploss",
dispo=dispo, # dispo=dispo,
pair=trade.pair, # pair=trade.pair,
rate=current_rate, # rate=current_rate,
trade_type=trade_type, # trade_type=trade_type,
profit=round(profit, 1), # profit=round(profit, 1),
buys=trade.nr_of_successful_entries + 1, # buys=trade.nr_of_successful_entries + 1,
stake=round(stake_amount, 2) # stake=round(stake_amount, 2)
) # )
#
self.pairs[trade.pair]['last_buy'] = current_rate # self.pairs[trade.pair]['last_buy'] = 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
#
return -stake_amount # return -stake_amount
if (self.wallets.get_available_stake_amount() < 10): # or trade.stake_amount >= max_stake: if (self.wallets.get_available_stake_amount() < 10): # or trade.stake_amount >= max_stake:
return 0 return 0
@@ -1145,7 +1223,7 @@ class FrictradeLearning(IStrategy):
return None return None
# Dernier prix d'achat réel (pas le prix moyen) # 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: # if len(trade.orders) > 0:
# # On cherche le dernier BUY exécuté # # On cherche le dernier BUY exécuté
@@ -1155,7 +1233,7 @@ class FrictradeLearning(IStrategy):
# baisse relative # baisse relative
# if minutes % 60 == 0: # 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 # self.pairs[pair]['last_ath'] = ath
# else: # else:
# ath = self.pairs[pair]['last_ath'] # ath = self.pairs[pair]['last_ath']
@@ -1166,9 +1244,10 @@ 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)
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 decline = (last_fill_price - current_rate) / last_fill_price
increase = - decline increase = - decline
@@ -1178,10 +1257,9 @@ class FrictradeLearning(IStrategy):
# stake_amount = last_amount * current_rate * 0.5 # stake_amount = last_amount * current_rate * 0.5
# return stake_amount # return stake_amount
########################### ALGO ATH ########################### ALGO ATH
# # --- 1. Calcul ATH local de la paire --- # # --- 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 --- # # --- 2. Distance ATH - current ---
# dd = (current_rate - ath) / ath * 100 # dd <= 0 # dd = (current_rate - ath) / ath * 100 # dd <= 0
@@ -1208,7 +1286,7 @@ class FrictradeLearning(IStrategy):
# return None # return None
# FIN ########################## ALGO ATH # 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 \ condition = last_candle['percent'] > 0 and last_candle['sma24_deriv1'] > 0 \
and last_candle['close'] < self.pairs[pair]['first_buy'] and last_candle['close'] < self.pairs[pair]['first_buy']
@@ -1219,11 +1297,12 @@ class FrictradeLearning(IStrategy):
self.pairs[pair]['previous_profit'] = profit self.pairs[pair]['previous_profit'] = profit
return None 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: # if force:
stake_amount = stake_amount / 4 # stake_amount = stake_amount / 2
# print(f"profit={profit} previous={self.pairs[pair]['previous_profit']} count_of_buys={trade.nr_of_successful_entries}") # self.printLog(f"profit={profit} previous={self.pairs[pair]['previous_profit']} count_of_buys={trade.nr_of_successful_entries}")
if stake_amount > 0: if stake_amount > 0:
self.pairs[pair]['previous_profit'] = profit self.pairs[pair]['previous_profit'] = profit
trade_type = "Loss " + (last_candle['enter_tag'] if last_candle['enter_long'] == 1 else '') trade_type = "Loss " + (last_candle['enter_tag'] if last_candle['enter_long'] == 1 else '')
@@ -1260,11 +1339,14 @@ class FrictradeLearning(IStrategy):
self.printLog(exception) self.printLog(exception)
return None 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: and last_candle['rsi'] < 75:
try: try:
self.pairs[pair]['previous_profit'] = profit 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: if stake_amount > 0:
self.pairs[pair]['has_gain'] += 1 self.pairs[pair]['has_gain'] += 1
@@ -1278,7 +1360,7 @@ class FrictradeLearning(IStrategy):
dispo=dispo, dispo=dispo,
pair=trade.pair, pair=trade.pair,
rate=current_rate, rate=current_rate,
trade_type='Gain ' + str(round(dca_threshold, 4)), trade_type='Gain ' + str(round(increase, 4)),
profit=round(profit, 1), profit=round(profit, 1),
buys=trade.nr_of_successful_entries + 1, buys=trade.nr_of_successful_entries + 1,
stake=round(stake_amount, 2) stake=round(stake_amount, 2)
@@ -1304,7 +1386,7 @@ class FrictradeLearning(IStrategy):
# before_last_candle_12 = dataframe.iloc[-13].squeeze() # before_last_candle_12 = dataframe.iloc[-13].squeeze()
# #
# expected_profit = self.expectedProfit(pair, last_candle) # 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 ----- # # ----- 1) Charger les variables de trailing pour ce trade -----
# max_price = self.pairs[pair]['max_touch'] # max_price = self.pairs[pair]['max_touch']
@@ -1315,7 +1397,7 @@ class FrictradeLearning(IStrategy):
count_of_buys = trade.nr_of_successful_entries 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: if current_profit > 0:
self.pairs[pair]['max_profit'] = max(self.pairs[pair]['max_profit'], profit) 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'] max_profit = self.pairs[pair]['max_profit']
# if current_profit > 0: # 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 # baisse = 0
# if profit > 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_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(price=current_rate,
ath=self.pairs[pair]['last_ath'],
nb_entries=count_of_buys)
# max_ = last_candle['max180'] # max_ = last_candle['max180']
# min_ = last_candle['min180'] # min_ = last_candle['min180']
# mid = last_candle['mid'] # mid = last_candle['mid']
@@ -1393,8 +1478,8 @@ class FrictradeLearning(IStrategy):
if last_candle['rsi'] > 90: if last_candle['rsi'] > 90:
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'] > last_candle['sma24']: # 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):
@@ -1402,27 +1487,28 @@ class FrictradeLearning(IStrategy):
# if (minutes > 1440 and last_candle['sma60_deriv1'] > 0) : # if (minutes > 1440 and last_candle['sma60_deriv1'] > 0) :
# return None # return None
# ----- 4) OFFSET : faut-il attendre de dépasser trailing_stop_positive_offset ? ----- # ----- 4) OFFSET : faut-il attendre de dépasser trailing_stop_positive_offset ? -----
if current_trailing_only_offset_is_reached and max_profit > current_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 # 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: 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)} " print(
f"{min(2, max_profit * (1 - current_trailing_stop_positive))}") 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 return None # ne pas activer le trailing encore
else: else:
print(f"{current_time} trailing atteint trailing_stop={round(trailing_stop,4)} profit={round(profit, 4)} max={round(max_profit, 4)} " print(
f"{min(2,max_profit * (1 - current_trailing_stop_positive))}") 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: else:
return None return None
# Sinon : trailing actif dès le début # Sinon : trailing actif dès le début
# ----- 6) Condition de vente ----- # ----- 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 self.pairs[pair]['force_buy'] = True
print( print(
f"{current_time} Condition de vente trailing_stop={round(trailing_stop,4)} profit={round(profit, 4)} max={round(max_profit, 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)}") 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 f"stop_{count_of_buys}_{self.pairs[pair]['has_gain']}"
return None return None
@@ -1499,7 +1585,6 @@ class FrictradeLearning(IStrategy):
# #
# return df # return df
def rsi_trend_probability(self, dataframe, short=6, long=12): def rsi_trend_probability(self, dataframe, short=6, long=12):
dataframe = dataframe.copy() dataframe = dataframe.copy()
@@ -1523,11 +1608,11 @@ class FrictradeLearning(IStrategy):
return pd.to_datetime(x, utc=True) return pd.to_datetime(x, utc=True)
# suppose self.btc_ath_history exists (liste de dict) # 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 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"]) ath_date = self.to_utc_ts(a["date"])
if ath_date <= candle_date: if ath_date <= candle_date:
if best is None or ath_date > best[0]: 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_rows', None)
pd.set_option('display.max_columns', None) pd.set_option('display.max_columns', None)
pd.set_option("display.width", 200) 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) os.makedirs(path, exist_ok=True)
# # Étape 1 : sélectionner numériques # # É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) best_f1 = max(f1_score(y_valid, (proba > t)) for t in thresholds)
return best_f1 return best_f1
study = optuna.create_study(direction="maximize") study = optuna.create_study(direction="maximize")
study.optimize(objective, n_trials=20) study.optimize(objective, n_trials=20)
@@ -1801,7 +1887,6 @@ class FrictradeLearning(IStrategy):
fig = plot_parallel_coordinate(study) fig = plot_parallel_coordinate(study)
fig.write_html(f"{self.path}/parallel_coordinates.html") fig.write_html(f"{self.path}/parallel_coordinates.html")
# 2⃣ Sélection des features AVANT calibration # 2⃣ Sélection des features AVANT calibration
sfm = SelectFromModel(self.train_model, threshold="median", prefit=True) sfm = SelectFromModel(self.train_model, threshold="median", prefit=True)
selected_features = X_train.columns[sfm.get_support()] 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") joblib.dump(self.train_model, f"{self.path}/{pair}_rf_model.pkl")
print(f"✅ Modèle sauvegardé sous {pair}_rf_model.pkl") print(f"✅ Modèle sauvegardé sous {pair}_rf_model.pkl")
# X = dataframe des features (après shift/rolling/indicators) # X = dataframe des features (après shift/rolling/indicators)
# y = target binaire ou décimale # y = target binaire ou décimale
# model = ton modèle entraîné (RandomForestClassifier ou Regressor) # 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('stop_buying')
and not c.startswith('target') and not c.startswith('target')
and not c.startswith('lvl') 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 # Étape 3 : remplacer inf et NaN par 0
dataframe[usable_cols] = dataframe[usable_cols].replace([np.inf, -np.inf], 0).fillna(0) dataframe[usable_cols] = dataframe[usable_cols].replace([np.inf, -np.inf], 0).fillna(0)
print("Colonnes utilisables pour le modèle :") # print("Colonnes utilisables pour le modèle :")
print(usable_cols) # print(usable_cols)
# self.model_indicators = usable_cols # self.model_indicators = usable_cols
return usable_cols return usable_cols
def select_uncorrelated_features(self, df, target, top_n=20, corr_threshold=0.7): 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, Sélectionne les features les plus corrélées avec target,
@@ -2295,11 +2383,12 @@ class FrictradeLearning(IStrategy):
factor1 = 100 * (ema_period / 5) factor1 = 100 * (ema_period / 5)
factor2 = 10 * (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}"]) & (dataframe[f"{name}{suffixe}"].shift(1) <= dataframe[f"{name}{suffixe}"])
# --- Distance à la moyenne mobile --- # --- 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 # dérivée relative simple
dataframe[d1_col] = (dataframe[name] - dataframe[name].shift(1)) / dataframe[name].shift(1) dataframe[d1_col] = (dataframe[name] - dataframe[name].shift(1)) / dataframe[name].shift(1)
@@ -2380,6 +2469,44 @@ class FrictradeLearning(IStrategy):
return dataframe 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): def calculModelInformative(self, informative):
# préparation # préparation
# print(df) # print(df)
@@ -2415,7 +2542,6 @@ class FrictradeLearning(IStrategy):
# joindre probabilités au df (dernières lignes correspondantes) # joindre probabilités au df (dernières lignes correspondantes)
return probas return probas
def prune_features(self, model, dataframe, feature_columns, importance_threshold=0.01): def prune_features(self, model, dataframe, feature_columns, importance_threshold=0.01):
""" """
Supprime les features dont l'importance est inférieure au seuil. 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) 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 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 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, def drawSequentialGraphs(self, model, history, X_train_scaled, X_valid_scaled, y_train,
y_valid, thresholds=None, best_threshold=None y_valid, thresholds=None, best_threshold=None
): ):
@@ -556,6 +596,17 @@ class Crash:
# plt.savefig(f"{self.path}/permutation_importance.png") # plt.savefig(f"{self.path}/permutation_importance.png")
# plt.close() # 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 # 7⃣ Sauvegarde seuil
# ========================= # =========================
@@ -567,7 +618,7 @@ class Crash:
"best_f1": max(f1s) "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): def objective(trial):
tf.keras.backend.clear_session() tf.keras.backend.clear_session()
@@ -835,7 +886,7 @@ class Crash:
# df['target'] = df['target'].fillna(0).astype(int) # df['target'] = df['target'].fillna(0).astype(int)
# label : crash si -n% dans les p heures # label : crash si -n% dans les p heures
self.calculateTarget(df) self.initTarget(df)
self.calculateCorrelation(df) self.calculateCorrelation(df)
@@ -872,7 +923,7 @@ class Crash:
assert len(X_train) == len(y_train) assert len(X_train) == len(y_train)
assert len(X_valid) == len(y_valid) 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) self.analyseStudy(study)
@@ -939,7 +990,7 @@ class Crash:
# plt.ylabel("Score") # plt.ylabel("Score")
# plt.show() # 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): def analyseImportances(self, selected_features, X_train, X_valid, y_valid):
# Feature importance # Feature importance
@@ -1137,7 +1188,7 @@ class Crash:
# FIN SHAP # FIN SHAP
def calculateTarget(self, df): def initTarget(self, df):
future = df['mid'].shift(-12) future = df['mid'].shift(-12)
df['future_dd'] = (future - df['mid']) / df['mid'] df['future_dd'] = (future - df['mid']) / df['mid']
df['target'] = (df['future_dd'] > 0.003).astype(int) df['target'] = (df['future_dd'] > 0.003).astype(int)
@@ -1214,7 +1265,7 @@ class Crash:
print("\n===== ✅ FIN DE LINSPECTION =====") 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). Analyse complète d'un modèle ML supervisé (classification binaire).
Affiche performances, importance des features, matrices, seuils, etc. Affiche performances, importance des features, matrices, seuils, etc.
@@ -1398,7 +1449,7 @@ class Crash:
df = df.set_index('date') df = df.set_index('date')
# Optionnel : ne garder quune plage temporelle # 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') df = df.reset_index('date')
# Supprimer NaN # Supprimer NaN