From d5e4c93947539695ea38ebccc1788c82247b93d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Delacotte?= Date: Wed, 29 Oct 2025 18:35:19 +0100 Subject: [PATCH] Zeus_8_3_2_B_4_2 test Paliers --- Zeus_8_1d.py | 29 +- Zeus_8_3_2_B_4_2.json | 26 +- Zeus_8_3_2_B_4_2.py | 1075 ++++++++++++++++++++++++----------------- 3 files changed, 678 insertions(+), 452 deletions(-) diff --git a/Zeus_8_1d.py b/Zeus_8_1d.py index 0a49ea7..6341357 100644 --- a/Zeus_8_1d.py +++ b/Zeus_8_1d.py @@ -29,6 +29,8 @@ from datetime import timezone, timedelta from scipy.signal import savgol_filter from ta.trend import SMAIndicator, EMAIndicator, MACD, ADXIndicator from collections import Counter +from scipy.signal import savgol_filter + logger = logging.getLogger(__name__) @@ -571,12 +573,14 @@ class Zeus_8_1d(IStrategy): dataframe['haclose'] = heikinashi['close'] dataframe['hapercent'] = (dataframe['haclose'] - dataframe['haopen']) / dataframe['haclose'] dataframe['hapercent3'] = (dataframe['haclose'] - dataframe['haopen'].shift(3)) / dataframe['haclose'].shift(3) + dataframe['mid'] = dataframe['haopen'] + (dataframe['haclose'] - dataframe['haopen']) / 2 + dataframe['sma5'] = dataframe["mid"].rolling(window=5).mean() #talib.SMA(informative, timeperiod=5) dataframe['sma5'] = talib.SMA(dataframe, timeperiod=5) self.calculeDerivees(dataframe, 'sma5', horizon=10) - dataframe['sma10'] = talib.SMA(dataframe, timeperiod=10) + dataframe['sma10'] = dataframe["mid"].rolling(window=10).mean() #dataframe['sma10'] = talib.SMA(dataframe, timeperiod=10) self.calculeDerivees(dataframe, 'sma10', horizon=10) - dataframe['sma20'] = talib.SMA(dataframe, timeperiod=20) + dataframe['sma20'] = dataframe["mid"].rolling(window=20).mean() #dataframe['sma20'] = talib.SMA(dataframe, timeperiod=20) self.calculeDerivees(dataframe, 'sma20', horizon=20) dataframe["percent"] = (dataframe["close"] - dataframe["open"]) / dataframe["open"] @@ -719,8 +723,8 @@ class Zeus_8_1d(IStrategy): informative['haclose'] = heikinashi['close'] informative['hapercent'] = (informative['haclose'] - informative['haopen']) / informative['haclose'] informative = self.calculateDerivation(informative, window=5, suffixe="_5") - informative['sma5'] = talib.SMA(informative, timeperiod=5) - informative['sma20'] = talib.SMA(informative, timeperiod=20) + informative['sma5'] = informative["mid"].rolling(window=5).mean() #talib.SMA(informative, timeperiod=5) + informative['sma20'] = informative["mid"].rolling(window=20).mean() #talib.SMA(informative, timeperiod=20) informative['max60'] = talib.MAX(informative['close'], timeperiod=60) informative['min60'] = talib.MIN(informative['close'], timeperiod=60) @@ -731,6 +735,23 @@ class Zeus_8_1d(IStrategy): self.calculateProbabilite2Index(informative, futur_cols, indic_1, indic_2) dataframe = merge_informative_pair(dataframe, informative, self.timeframe, "1d", ffill=True) + # --- pente brute --- + dataframe['slope'] = dataframe['sma20'].diff() + + # --- lissage EMA --- + dataframe['slope_smooth'] = dataframe['slope'].ewm(span=10, adjust=False).mean() + + # --- normalisation relative --- + dataframe['slope_norm'] = 100 * dataframe['slope_smooth'] / dataframe['close'] + # df['slope_norm'].fillna(0, inplace=True) + dataframe['slope_norm'] = dataframe['slope_norm'].fillna(0) + + # EMA plus réactive (span=5) + dataframe["ema5"] = dataframe["mid_smooth_5"].ewm(span=5, adjust=False).mean() + + # EMA plus lissée (span=20) + dataframe["ema20"] = dataframe["mid"].ewm(span=20, adjust=False).mean() + return dataframe def calculeDerivees(self, dataframe, indic, factor_1=100, factor_2=10, horizon=5): diff --git a/Zeus_8_3_2_B_4_2.json b/Zeus_8_3_2_B_4_2.json index 84797c1..f39a47e 100644 --- a/Zeus_8_3_2_B_4_2.json +++ b/Zeus_8_3_2_B_4_2.json @@ -17,17 +17,27 @@ "max_open_trades": 80 }, "buy": { - "buy_horizon_predict_1h": 2, - "mise_factor_buy": 0.06 + "indic_5m": "mid_smooth_5", + "indic_deriv1_5m": -1.52, + "indic_deriv2_5m": -0.15, + "mise_factor_buy": 0.1, + "mises": 10, + "pct": 0.012, + "pct_inc": 0.005 + }, + "sell": { + "indic_5m_sell": "mid_smooth_24", + "indic_deriv1_5m_sell": -1.93, + "indic_deriv2_5m_sell": -1.4 }, - "sell": {}, "protection": { - "sma5_deriv1_1d_restart_protection": 2.2, - "sma5_deriv1_1d_stop_protection": -3.9, - "sma5_deriv2_1d_restart_protection": 0.0, - "sma5_deriv2_1d_stop_protection": -4.8 + "indic_1d_p": "mid_smooth_24", + "indic_deriv1_1d_p_start": -1.9, + "indic_deriv1_1d_p_stop": 1.1, + "indic_deriv2_1d_p_start": 3.7, + "indic_deriv2_1d_p_stop": -0.9 } }, "ft_stratparam_v": 1, - "export_time": "2025-09-28 13:58:28.838866+00:00" + "export_time": "2025-10-26 16:32:08.937921+00:00" } \ No newline at end of file diff --git a/Zeus_8_3_2_B_4_2.py b/Zeus_8_3_2_B_4_2.py index 2d03fc5..123f2ec 100644 --- a/Zeus_8_3_2_B_4_2.py +++ b/Zeus_8_3_2_B_4_2.py @@ -11,12 +11,16 @@ from freqtrade.strategy import (BooleanParameter, CategoricalParameter, DecimalP IntParameter, IStrategy, merge_informative_pair, informative, stoploss_from_absolute) import pandas as pd import numpy as np +import os +import json from pandas import DataFrame from typing import Optional, Union, Tuple - +import math import logging import configparser from technical import pivots_points +from pathlib import Path + # -------------------------------- # Add your lib to import here test git @@ -76,6 +80,7 @@ class Zeus_8_3_2_B_4_2(IStrategy): max_open_trades = 5 max_amount = 40 + parameters = {} # DCA config position_adjustment_enable = True @@ -149,6 +154,9 @@ class Zeus_8_3_2_B_4_2(IStrategy): } }, "States": { + "tdc_macd_1h": { + "color": "cyan" + }, "sma24_state_1h": { "color": "pink" }, @@ -161,6 +169,17 @@ class Zeus_8_3_2_B_4_2(IStrategy): "sma60_state": { "color": "green" } + }, + 'Macd': { + "macd_rel_1d": { + "color": "cyan" + }, + "macdsignal_rel_1d": { + "color": "pink" + }, + "macdhist_rel_1d": { + "color": "yellow" + } } } } @@ -214,35 +233,51 @@ class Zeus_8_3_2_B_4_2(IStrategy): trades = list() max_profit_pairs = {} - # sma24_deriv1_1d_stop_protection = DecimalParameter(-0.2, 0.2, default=0.05, decimals=2, space='protection', - # optimize=True, load=True) - sma5_deriv1_1d_stop_protection = DecimalParameter(-5, 0, default=0.5, decimals=1, space='protection', - optimize=True, load=True) - sma5_deriv2_1d_stop_protection = DecimalParameter(-5, 0, default=0.5, decimals=1, space='protection', optimize=True, - load=True) + # # sma24_deriv1_1d_stop_protection = DecimalParameter(-0.2, 0.2, default=0.05, decimals=2, space='protection', + # # optimize=True, load=True) + # sma5_deriv1_1d_stop_protection = DecimalParameter(-5, 0, default=0.5, decimals=1, space='protection', + # optimize=True, load=True) + # sma5_deriv2_1d_stop_protection = DecimalParameter(-5, 0, default=0.5, decimals=1, space='protection', optimize=True, + # load=True) + # + # # sma24_deriv1_1d_start_protection = DecimalParameter(-0.2, 0.2, default=0.05, decimals=2, space='protection', + # # optimize=True, load=True) + # sma5_deriv1_1d_restart_protection = DecimalParameter(0, 5, default=0.5, decimals=1, space='protection', + # optimize=True, load=True) + # sma5_deriv2_1d_restart_protection = DecimalParameter(0, 5, default=0.5, decimals=1, space='protection', + # optimize=True, + # load=True) + # + mise_factor_buy = DecimalParameter(0.01, 0.1, default=0.05, decimals=2, space='buy', optimize=True, load=True) - # sma24_deriv1_1d_start_protection = DecimalParameter(-0.2, 0.2, default=0.05, decimals=2, space='protection', - # optimize=True, load=True) - sma5_deriv1_1d_restart_protection = DecimalParameter(0, 5, default=0.5, decimals=1, space='protection', - optimize=True, load=True) - sma5_deriv2_1d_restart_protection = DecimalParameter(0, 5, default=0.5, decimals=1, space='protection', - optimize=True, - load=True) + indicators = {'sma5', 'sma12', 'sma24', 'sma60', 'mid_smooth_3', 'mid_smooth_5', 'mid_smooth_12', 'mid_smooth_24'} - mise_factor_buy = DecimalParameter(0.01, 0.2, default=0.05, decimals=2, space='buy', optimize=True, load=True) + mises = IntParameter(5, 50, default=10, space='buy', optimize=False, load=False) - sma5_deriv1_1d_stop_sell = DecimalParameter(-5, 5, default=0.5, decimals=1, space='sell', - optimize=True, load=True) - sma5_deriv2_1d_stop_sell = DecimalParameter(-5, 5, default=0.5, decimals=1, space='sell', optimize=True, - load=True) - sma5_deriv1_1h_stop_sell = DecimalParameter(-5, 5, default=0.5, decimals=1, space='sell', - optimize=True, load=True) - sma5_deriv2_1h_stop_sell = DecimalParameter(-5, 5, default=0.5, decimals=1, space='sell', optimize=True, - load=True) - # Récupération des labels ordonnés - # labels = ['B5', 'B4', 'B3', 'B2', 'B1', 'N0', 'H1', 'H2', 'H3', 'H4', 'H5'] - # index_labels = ['B5', 'B4', 'B3', 'B2', 'B1', 'N0', 'H1', 'H2', 'H3', 'H4', 'H5'] - # ordered_labels = ['B5', 'B4', 'B3', 'B2', 'B1', 'N0', 'H1', 'H2', 'H3', 'H4', 'H5'] + pct = DecimalParameter(0.005, 0.05, default=0.012, decimals=3, space='buy', optimize=False, load=False) + pct_inc = DecimalParameter(0.0001, 0.003, default=0.005, decimals=4, space='buy', optimize=False, load=False) + + indic_5m = CategoricalParameter(indicators, default="sma60", space='buy') + indic_deriv1_5m = DecimalParameter(-2, 2, default=0, decimals=2, space='buy', optimize=True, load=True) + indic_deriv2_5m = DecimalParameter(-2, 2, default=0, decimals=2, space='buy', optimize=True, load=True) + + # indic_1h = CategoricalParameter(indicators, default="sma60", space='buy') + # indic_deriv1_1h = DecimalParameter(-5, 5, default=0, decimals=1, space='buy', optimize=True, load=True) + # indic_deriv2_1h = DecimalParameter(-10, 10, default=0, decimals=1, space='buy', optimize=True, load=True) + + indic_1d_p = CategoricalParameter(indicators, default="sma60", space='protection') + indic_deriv1_1d_p_stop = DecimalParameter(-2, 2, default=0, decimals=1, space='protection', optimize=True, load=True) + indic_deriv2_1d_p_stop = DecimalParameter(-4, 4, default=0, decimals=1, space='protection', optimize=True, load=True) + indic_deriv1_1d_p_start = DecimalParameter(-2, 2, default=0, decimals=1, space='protection', optimize=True, load=True) + indic_deriv2_1d_p_start = DecimalParameter(-4, 4, default=0, decimals=1, space='protection', optimize=True, load=True) + + + indic_5m_sell = CategoricalParameter(indicators, default="sma60", space='sell') + indic_deriv1_5m_sell = DecimalParameter(-2, 2, default=0, decimals=2, space='sell', optimize=True, load=True) + indic_deriv2_5m_sell = DecimalParameter(-2, 2, default=0, decimals=2, space='sell', optimize=True, load=True) + + # indic_deriv1_1h_sell = DecimalParameter(-5, 5, default=0, decimals=1, space='sell', optimize=True, load=True) + # indic_deriv2_1h_sell = DecimalParameter(-10, 10, default=0, decimals=1, space='sell', optimize=True, load=True) labels = ['B3', 'B2', 'B1', 'N0', 'H1', 'H2', 'H3'] index_labels = ['B3', 'B2', 'B1', 'N0', 'H1', 'H2', 'H3'] @@ -250,32 +285,6 @@ class Zeus_8_3_2_B_4_2(IStrategy): label_to_index = {label: i for i, label in enumerate(ordered_labels)} - # # ========================================================================= - # # variables pour probabilité - # # Bornes des quantiles pour - # ema_volume = [-8.9178, -0.0196, -0.0096, -0.0053, -0.0026, -0.0007, 0.0009, 0.0029, 0.0056, 0.0101, 0.0200, 3.8009] - # # Bornes des quantiles pour - # mid_smooth_1h_deriv1 = [-1.0482, -0.0571, -0.0336, -0.0206, -0.0113, -0.0033, 0.0044, 0.0127, 0.0225, 0.0356, 0.0591, 0.8335] - # - # ema_volume_mid_smooth_1h_deriv1_matrice = { - # 'B5': [28.0, 32.8, 33.6, 36.4, 35.5, 35.6, 40.1, 40.9, 45.9, 49.7, 52.2], - # 'B4': [33.9, 37.2, 38.6, 40.7, 39.7, 43.0, 46.2, 47.1, 51.9, 55.9, 61.1], - # 'B3': [36.4, 41.3, 39.1, 41.8, 44.6, 46.1, 50.3, 47.9, 47.6, 57.0, 58.5], - # 'B2': [40.7, 40.6, 40.9, 44.6, 48.0, 48.4, 48.5, 53.5, 53.0, 54.8, 53.3], - # 'B1': [37.5, 41.4, 48.0, 46.3, 48.5, 49.1, 53.7, 53.4, 56.4, 56.7, 62.8], - # 'N0': [47.0, 44.3, 45.6, 47.0, 52.9, 52.2, 55.7, 53.0, 57.6, 58.1, 63.4], - # 'H1': [44.1, 46.2, 49.4, 49.3, 52.2, 53.7, 58.2, 57.1, 59.0, 61.6, 61.3], - # 'H2': [51.0, 44.7, 49.4, 51.3, 54.9, 57.9, 56.7, 58.1, 60.3, 60.6, 65.6], - # 'H3': [50.5, 48.3, 49.9, 60.4, 57.8, 56.3, 60.2, 61.9, 62.2, 65.3, 68.3], - # 'H4': [43.1, 53.6, 58.1, 61.4, 58.7, 62.6, 61.3, 65.4, 67.5, 68.2, 71.4], - # 'H5': [56.6, 56.2, 57.7, 63.8, 64.8, 64.7, 66.5, 68.8, 70.9, 72.8, 76.6], - # - # } - # - # ema_volume_mid_smooth_1h_deriv1_matrice_df = pd.DataFrame(ema_volume_mid_smooth_1h_deriv1_matrice, index=index_labels) - # # Extraction de la matrice numérique - # ema_volume_mid_smooth_1h_deriv1_numeric_matrice = ema_volume_mid_smooth_1h_deriv1_matrice_df.reindex(index=ordered_labels, columns=ordered_labels).values - # ========================================================================= # paliers dérivées jour sma5 sma5_deriv1 = [-1.1726, -0.2131, -0.1012, -0.0330, 0.0169, 0.0815, 0.2000, 4.0335] @@ -302,7 +311,7 @@ class Zeus_8_3_2_B_4_2(IStrategy): # buy_mid_smooth_3_deriv1 = DecimalParameter(-0.1, 0.1, decimals=2, default=-0.06, space='buy') # buy_mid_smooth_24_deriv1 = DecimalParameter(-0.6, 0, decimals=2, default=-0.03, space='buy') - buy_horizon_predict_1h = IntParameter(1, 6, default=2, space='buy') + # buy_horizon_predict_1h = IntParameter(1, 6, default=2, space='buy') # buy_level_predict_1h = IntParameter(2, 5, default=4, space='buy') @@ -321,9 +330,18 @@ class Zeus_8_3_2_B_4_2(IStrategy): last_candle_3 = dataframe.iloc[-3].squeeze() # val = self.getProbaHausse144(last_candle) + trend = last_candle['trend_class'] + # params = self.loadParamsFor(pair, trend) + + indic_5m = self.getParamValue(pair, trend, 'buy', 'indic_5m') + indic_deriv1_5m = self.getParamValue( pair, trend, 'buy', 'indic_deriv1_5m') + indic_deriv2_5m = self.getParamValue( pair, trend, 'buy', '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 #(not self.stop_all) #& (not self.all_down) # and val > self.buy_val.value #not last_candle['tendency'] in ('B-', 'B--') # (rate <= float(limit)) | (entry_tag == 'force_entry') - allow_to_buy = not self.pairs[pair]['stop'] | (entry_tag == 'force_entry') + allow_to_buy = (condition and not self.pairs[pair]['stop']) | (entry_tag == 'force_entry') # if allow_to_buy: # poly_func, x_future, y_future, count = self.polynomial_forecast( @@ -467,6 +485,12 @@ class Zeus_8_3_2_B_4_2(IStrategy): days_since_first_buy = (current_time - trade.open_date_utc).days hours = (current_time - trade.date_last_filled_utc).total_seconds() / 3600.0 + trend = last_candle['trend_class'] + + indic_5m_sell = self.getParamValue( pair, trend, 'sell', 'indic_5m_sell') + indic_deriv1_5m_sell = self.getParamValue( pair, trend, 'sell', 'indic_deriv1_5m_sell') + indic_deriv2_5m_sell = self.getParamValue( pair, trend, 'sell', 'indic_deriv2_5m_sell') + if hours % 4 == 0: self.log_trade( last_candle=last_candle, @@ -477,7 +501,7 @@ class Zeus_8_3_2_B_4_2(IStrategy): rate=last_candle['close'], trade_type='', profit=profit, - buys='', + buys=count_of_buys, stake=0 ) @@ -491,13 +515,34 @@ class Zeus_8_3_2_B_4_2(IStrategy): pair_name = self.getShortName(pair) # if (current_profit > expected_profit) and last_candle['can_sell']: # return 'Can_' + pair_name + '_' + str(count_of_buys) + trend = last_candle['trend_class_1d'] + if (trend == "BR1" or trend == "BR2") and self.pairs[pair]['has_gain'] == 0: # and (last_candle[f"{indic_5m_sell}_deriv1"] <= indic_deriv1_5m_sell and last_candle[f"{indic_5m_sell}_deriv2"] <= indic_deriv2_5m_sell): + + if (last_candle['max_rsi_12_1h'] > 75) and last_candle['trend_class_1h'] == 'BU1' and profit > max(5, expected_profit) and (last_candle['hapercent'] < 0): + self.pairs[pair]['stop'] = True + self.log_trade( + last_candle=last_candle, + date=current_time, + action="🔴STOP", + dispo=dispo, + pair=pair, + rate=last_candle['close'], + trade_type='', + profit=self.pairs[pair]['current_profit'], + buys=self.pairs[pair]['count_of_buys'], + stake=0 + ) + return "MAX_RSI" - if last_candle['sma24_deriv2_1h'] > 0: return None + # if (trend == "BR1" or trend == "BR2") and last_candle[f"{self.indic_5m_sell.value}_deriv1"] <= self.indic_deriv1_5m_sell.value \ + # and last_candle[f"{self.indic_5m_sell.value}_deriv2"] <= self.indic_deriv2_5m_sell.value: + # return None + if last_candle['max_rsi_24'] > 85 and profit > max(5, expected_profit) and (last_candle['hapercent'] < 0) and last_candle['sma60_deriv1'] < 0.05: self.pairs[pair]['force_sell'] = False - self.pairs[pair]['force_buy'] = (self.pairs[pair]['count_of_buys'] - self.pairs[pair]['has_gain'] > 3) + self.pairs[pair]['force_buy'] = False #(self.pairs[pair]['count_of_buys'] - self.pairs[pair]['has_gain'] > 3) return str(count_of_buys) + '_' + 'Rsi85_' + pair_name + '_' + str(self.pairs[pair]['has_gain']) if self.pairs[pair]['force_sell']: @@ -559,7 +604,7 @@ class Zeus_8_3_2_B_4_2(IStrategy): self.pairs[pair]['max_touch'] = max(last_candle['close'], self.pairs[pair]['max_touch']) def getShortName(self, pair): - return pair.replace("/USDT", '').replace("/USDC", '') + return pair.replace("/USDT", '').replace("/USDC", '').replace("_USDC", '').replace("_USDT", '') def informative_pairs(self): # get access to all pairs available in whitelist. @@ -667,6 +712,7 @@ class Zeus_8_3_2_B_4_2(IStrategy): color = GREEN if profit > 0 else RED color_sma24 = GREEN if last_candle['sma24_deriv1_1d'] > 0 else RED + color_sma24_2 = GREEN if last_candle['sma24_deriv2_1d'] > 0 else RED color_sma5 = GREEN if last_candle['mid_smooth_5_deriv1_1d'] > 0 else RED color_sma5_2 = GREEN if last_candle['mid_smooth_5_deriv2_1d'] > 0 else RED color_sma5_1h = GREEN if last_candle['sma60_deriv1'] > 0 else RED @@ -685,6 +731,16 @@ class Zeus_8_3_2_B_4_2(IStrategy): # 🟡 Dérivée 1 > 0 et dérivée 2 < 0: tendance haussière qui ralentit → essoufflement potentiel. # 🔴 Dérivée 1 < 0 et dérivée 2 < 0: tendance baissière qui s’accélère. # 🟠 Dérivée 1 < 0 et dérivée 2 > 0: tendance baissière qui ralentit → possible bottom. + trend = last_candle['trend_class_1d'] + + indic_5m = self.getParamValue(pair, trend, 'buy', 'indic_5m') + indic_deriv1_5m = self.getParamValue(pair, trend, 'buy', 'indic_deriv1_5m') + indic_deriv2_5m = self.getParamValue(pair, trend, 'buy', 'indic_deriv2_5m') + + indic_5m_sell = self.getParamValue(pair, trend, 'sell', 'indic_5m_sell') + indic_deriv1_5m_sell = self.getParamValue(pair, trend, 'sell', 'indic_deriv1_5m_sell') + indic_deriv2_5m_sell = self.getParamValue(pair, trend, 'sell', 'indic_deriv2_5m_sell') + self.printLog( f"| {date:<16} |{action:<10} | {pair[0:3]:<3} | {trade_type or '-':<18} |{rate or '-':>9}| {dispo or '-':>6} " @@ -700,6 +756,7 @@ class Zeus_8_3_2_B_4_2(IStrategy): # f"|{last_candle['min60_1d']}|{last_candle['max60_1d']}" # f"|{last_candle['mid_smooth_tdc_5_1d'] or '-':>3}|{last_candle['mid_smooth_tdc_5_1h'] or '-':>3}|{last_candle['mid_smooth_tdc_5'] or '-':>3}" f"|{last_candle['mid_smooth_5_state_1d'] or '-':>3}|{last_candle['mid_smooth_24_state_1h'] or '-':>3}|{last_candle['mid_smooth_5_state_1h'] or '-':>3}|{last_candle['mid_smooth_5_state'] or '-':>3}" + f"|Params {last_candle['trend_class_1d']} {last_candle['trend_class_1h']} {indic_5m} {indic_deriv1_5m} {indic_deriv2_5m} {indic_5m_sell} {indic_deriv1_5m_sell} {indic_deriv2_5m_sell}" ) def getLastLost(self, last_candle, pair): @@ -790,10 +847,6 @@ class Zeus_8_3_2_B_4_2(IStrategy): ################### INFORMATIVE 1d informative = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe="1d") informative = self.populateDataframe(informative, timeframe='1d') - - if self.dp.runmode.value in ('backtest'): - informative['futur_percent'] = 100 * (informative['close'].shift(-1) - informative['close']) / informative[ - 'close'] dataframe = merge_informative_pair(dataframe, informative, self.timeframe, "1d", ffill=True) dataframe['last_price'] = dataframe['close'] @@ -816,6 +869,7 @@ class Zeus_8_3_2_B_4_2(IStrategy): if count == 0: dataframe['first_price'] = buy.price self.pairs[pair]['first_buy'] = buy.price + self.pairs[pair]['first_amount'] = buy.price * buy.filled # dataframe['close01'] = buy.price * 1.01 # Order(id=2396, trade=1019, order_id=29870026652, side=buy, filled=0.00078, price=63921.01, @@ -827,7 +881,7 @@ class Zeus_8_3_2_B_4_2(IStrategy): # dataframe['mid_price'] = (dataframe['last_price'] + dataframe['first_price']) / 2 count_buys = count # dataframe['limit'] = dataframe['last_price'] * (1 - self.baisse[count] / 100) - # dataframe['amount'] = amount + self.pairs[pair]['total_amount'] = amount # dataframe['mid_smooth_tag'] = qtpylib.crossed_below(dataframe['mid_smooth_24_deriv1'], dataframe['mid_smooth_deriv2_24']) @@ -846,36 +900,6 @@ class Zeus_8_3_2_B_4_2(IStrategy): # Compter les baisses / hausses consécutives # self.calculateDownAndUp(dataframe, limit=0.0001) - # dataframe["mid_re_smooth_1h"] = self.conditional_smoothing(dataframe['mid_smooth_1h'].dropna(), threshold=0.0005).dropna() - # self.calculeDerivees(dataframe, "mid_re_smooth_1h") - - # dataframe['close_smooth_1h'] = self.conditional_smoothing(dataframe['mid'].rolling(window=3).mean().dropna(), threshold=0.0005) - # dataframe['smooth_1h'], dataframe['deriv1_1h'], dataframe['deriv2_1h'] = self.smooth_and_derivatives(dataframe['close_smooth_1h']) - # dataframe['deriv1_1h'] = 100 * dataframe['deriv1_1h'] / dataframe['mid_smooth_1h'] - # dataframe['deriv2_1h'] = 1000 * dataframe['deriv2_1h'] / dataframe['mid_smooth_1h'] - - horizon_h = 12 - # dataframe['sma5_1h'] = dataframe['sma5_1h'].rolling(window=horizon_h).mean() - # dataframe['ema_volume'] = dataframe['ema_volume'].rolling(window=horizon_h).mean() - # dataframe['sma24_1h'] = dataframe['sma24_1h'].rolling(window=horizon_h).mean() - # dataframe['sma24_deriv1_1h'] = dataframe['sma24_deriv1_1h'].rolling(window=horizon_h).mean() - - # dataframe = self.calculateRegression(dataframe, column='mid_smooth_1h', window=horizon_h * 12, degree=4, future_offset=24) - - # Suppose que df['close'] est ton prix de clôture - - # dataframe['close_smooth_24'] = self.conditional_smoothing(dataframe['mid'].rolling(24).mean().dropna(), threshold=0.0015) - # dataframe['smooth_24'], dataframe['smooth_24_deriv1'], dataframe['smooth_24_deriv2'] = self.smooth_and_derivatives(dataframe['close_smooth_24']) - # dataframe['smooth_24_deriv1'] = 100 * dataframe['smooth_24_deriv1'] / dataframe['mid_smooth_24'] - # dataframe['smooth_24_deriv2'] = 100 * dataframe['smooth_24_deriv2'] / dataframe['mid_smooth_24'] - - dataframe['close_smooth'] = self.conditional_smoothing(dataframe['mid'].rolling(3).mean().dropna(), - threshold=0.001) - dataframe['smooth'], dataframe['deriv1'], dataframe['deriv2'] = self.smooth_and_derivatives( - dataframe['close_smooth']) - dataframe['deriv1'] = 100 * dataframe['deriv1'] / dataframe['mid'] - dataframe['deriv2'] = 100 * dataframe['deriv2'] / dataframe['mid'] - # =============================== # Lissage des valeurs Journalières horizon_d = 12 * 5 * 24 @@ -931,14 +955,20 @@ class Zeus_8_3_2_B_4_2(IStrategy): dataframe['hapercent'] = (dataframe['haclose'] - dataframe['haopen']) / dataframe['haclose'] dataframe['mid'] = dataframe['haopen'] + (dataframe['haclose'] - dataframe['haopen']) / 2 + dataframe["percent"] = (dataframe["close"] - dataframe["open"]) / dataframe["open"] + dataframe["percent3"] = (dataframe["close"] - dataframe["open"].shift(3)) / dataframe["open"].shift(3) + + if self.dp.runmode.value in ('backtest'): + dataframe['futur_percent'] = 100 * (dataframe['close'].shift(-1) - dataframe['close']) / dataframe['close'] + # dataframe['hapercent3'] = (dataframe['haclose'] - dataframe['haopen'].shift(3)) / dataframe['haclose'].shift(3) - dataframe['sma5'] = talib.SMA(dataframe, timeperiod=5) + dataframe['sma5'] = dataframe["mid"].rolling(window=5).mean() #talib.SMA(dataframe, timeperiod=5) self.calculeDerivees(dataframe, 'sma5', timeframe=timeframe, ema_period=5) - dataframe['sma12'] = talib.SMA(dataframe, timeperiod=12) + dataframe['sma12'] = dataframe["mid"].rolling(window=12).mean() #talib.SMA(dataframe, timeperiod=12) self.calculeDerivees(dataframe, 'sma12', timeframe=timeframe, ema_period=12) - dataframe['sma24'] = talib.SMA(dataframe, timeperiod=24) + dataframe['sma24'] = dataframe["mid"].rolling(window=24).mean() #talib.SMA(dataframe, timeperiod=24) self.calculeDerivees(dataframe, 'sma24', timeframe=timeframe, ema_period=24) - dataframe['sma60'] = talib.SMA(dataframe, timeperiod=60) + dataframe['sma60'] = dataframe["mid"].rolling(window=60).mean() #talib.SMA(dataframe, timeperiod=60) self.calculeDerivees(dataframe, 'sma60', timeframe=timeframe, ema_period=60) dataframe = self.calculateDerivation(dataframe, window=3, suffixe="_3",timeframe=timeframe) @@ -963,24 +993,130 @@ class Zeus_8_3_2_B_4_2(IStrategy): dataframe['bb_middleband'] = bollinger['mid'] dataframe['bb_upperband'] = bollinger['upper'] - # macd, macdsignal, macdhist = talib.MACD( - # dataframe['close'], - # fastperiod=12, - # slowperiod=26, - # signalperiod=9 - # ) + # Calcul MACD + macd, macdsignal, macdhist = talib.MACD( + dataframe['close'], + fastperiod=12, + slowperiod=26, + signalperiod=9 + ) + + # | Nom | Formule / définition | Signification | + # | ---------------------------- | ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | + # | **MACD** (`macd`) | `EMA_fast - EMA_slow` (ex : 12-26 périodes) | Montre l’écart entre la moyenne courte et la moyenne longue.
- Positive → tendance haussière
- Négative → tendance baissière | + # | **Signal** (`macdsignal`) | `EMA_9(MACD)` | Sert de ligne de **signal de déclenchement**.
- Croisement du MACD au-dessus → signal d’achat
- Croisement du MACD en dessous → signal de vente | + # | **Histogramme** (`macdhist`) | `MACD - Signal` | Montre la **force et l’accélération** de la tendance.
- Positif et croissant → tendance haussière qui s’accélère
- Positif mais décroissant → ralentissement de la hausse
- Négatif et décroissant → baisse qui s’accélère
- Négatif mais croissant → ralentissement de la baisse | + + # Ajouter dans le dataframe + dataframe['macd'] = macd + dataframe['macdsignal'] = macdsignal + dataframe['macdhist'] = macdhist + + # --- Rendre relatif sur chaque série (-1 → 1) --- + for col in ['macd', 'macdsignal', 'macdhist']: + series = dataframe[col] + valid = series[~np.isnan(series)] # ignorer NaN + min_val = valid.min() + max_val = valid.max() + span = max_val - min_val if max_val != min_val else 1 + dataframe[f'{col}_rel'] = 2 * ((series - min_val) / span) - 1 + + dataframe['tdc_macd'] = self.macd_tendance_int( + dataframe, + macd_col='macd_rel', + signal_col='macdsignal_rel', + hist_col='macdhist_rel' + ) + + # ------------------------------------------------------------------------------------ + # rolling SMA indicators (used for trend detection too) + s_short = self.DEFAULT_PARAMS['sma_short'] + s_long = self.DEFAULT_PARAMS['sma_long'] + + dataframe[f'sma_{s_short}'] = dataframe['close'].rolling(window=s_short).mean() + dataframe[f'sma_{s_long}'] = dataframe['close'].rolling(window=s_long).mean() + + # --- pente brute --- + dataframe['slope'] = dataframe['sma24'].diff() + + # --- lissage EMA --- + dataframe['slope_smooth'] = dataframe['slope'].ewm(span=10, adjust=False).mean() + + # # RSI + # window = 14 + # delta = dataframe['close'].diff() + # up = delta.clip(lower=0) + # down = -1 * delta.clip(upper=0) + # ma_up = up.rolling(window=window).mean() + # ma_down = down.rolling(window=window).mean() + # rs = ma_up / ma_down.replace(0, 1e-9) + # dataframe['rsi'] = 100 - (100 / (1 + rs)) # - # dataframe['macd'] = macd - # dataframe['macdsignal'] = macdsignal - # dataframe['macdhist'] = macdhist + # # EMA example + # dataframe['ema'] = dataframe['close'].ewm(span=self.DEFAULT_PARAMS['ema_period'], adjust=False).mean() # - # dataframe['ema_volume'] = 20 * (dataframe['volume'] * dataframe['percent']) / ( - # abs(dataframe['volume'].shift(1)) + abs(dataframe['volume'].shift(2))) - # - # self.calculeDerivees(dataframe, 'ema_volume', factor_1=10, factor_2=1, timeframe=timeframe, ema_period=14) + # # ATR (simple implementation) + # high_low = dataframe['high'] - dataframe['low'] + # high_close = (dataframe['high'] - dataframe['close'].shift()).abs() + # low_close = (dataframe['low'] - dataframe['close'].shift()).abs() + # tr = DataFrame({'hl': high_low, 'hc': high_close, 'lc': low_close}).max(axis=1) + # dataframe['atr'] = tr.rolling(window=self.DEFAULT_PARAMS['atr_period']).mean() + + self.setTrends(dataframe) return dataframe + def macd_tendance_int(self, dataframe: pd.DataFrame, + macd_col='macd', + signal_col='macdsignal', + hist_col='macdhist', + eps=0.0) -> pd.Series: + """ + Renvoie la tendance MACD sous forme d'entiers. + 2 : Haussier + 1 : Ralentissement hausse + 0 : Neutre + -1 : Ralentissement baisse + -2 : Baissier + """ + + # | Nom | Formule / définition | Signification | + # | ---------------------------- | ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | + # | **MACD** (`macd`) | `EMA_fast - EMA_slow` (ex : 12-26 périodes) | Montre l’écart entre la moyenne courte et la moyenne longue.
- Positive → tendance haussière
- Négative → tendance baissière | + # | **Signal** (`macdsignal`) | `EMA_9(MACD)` | Sert de ligne de **signal de déclenchement**.
- Croisement du MACD au-dessus → signal d’achat
- Croisement du MACD en dessous → signal de vente | + # | **Histogramme** (`macdhist`) | `MACD - Signal` | Montre la **force et l’accélération** de la tendance.
- Positif et croissant → tendance haussière qui s’accélère
- Positif mais décroissant → ralentissement de la hausse
- Négatif et décroissant → baisse qui s’accélère
- Négatif mais croissant → ralentissement de la baisse | + + # | Situation | MACD | Signal | Hist | Interprétation | + # | -------------------------- | ---------- | --------- | -------- | ------------------------------------------ | + # | MACD > 0, Hist croissant | au-dessus | croissant | Haussier | Momentum fort → tendance haussière | + # | MACD > 0, Hist décroissant | au-dessus | en baisse | Momentum | La hausse ralentit, prudence | + # | MACD < 0, Hist décroissant | en dessous | en baisse | Baissier | Momentum fort → tendance baissière | + # | MACD < 0, Hist croissant | en dessous | en hausse | Rebond ? | La baisse ralentit → possible retournement | + + # Créer une série de 0 par défaut + tendance = pd.Series(0, index=dataframe.index) + + # Cas MACD > signal + mask_up = dataframe[macd_col] > dataframe[signal_col] + eps + mask_up_hist_pos = mask_up & (dataframe[hist_col] > 0) + mask_up_hist_neg = mask_up & (dataframe[hist_col] <= 0) + + tendance[mask_up_hist_pos] = 2 # Haussier + tendance[mask_up_hist_neg] = 1 # Ralentissement hausse + + # Cas MACD < signal + mask_down = dataframe[macd_col] < dataframe[signal_col] - eps + mask_down_hist_neg = mask_down & (dataframe[hist_col] < 0) + mask_down_hist_pos = mask_down & (dataframe[hist_col] >= 0) + + tendance[mask_down_hist_neg] = -2 # Baissier + tendance[mask_down_hist_pos] = -1 # Ralentissement baisse + + # Les NaN deviennent neutre + tendance[dataframe[[macd_col, signal_col, hist_col]].isna().any(axis=1)] = 0 + + return tendance + def calculateDownAndUp(self, dataframe, limit=0.0001): dataframe['down'] = dataframe['mid_smooth_1h_deriv1'] < limit # dataframe['hapercent'] <= limit dataframe['up'] = dataframe['mid_smooth_1h_deriv1'] > limit # dataframe['hapercent'] >= limit @@ -993,7 +1129,6 @@ class Zeus_8_3_2_B_4_2(IStrategy): dataframe['up_pct'] = self.calculateUpDownPct(dataframe, 'up_count') def calculateDerivation(self, dataframe, window=12, suffixe='', timeframe='5m'): - dataframe['mid'] = dataframe['haopen'] + (dataframe['haclose'] - dataframe['haopen']) / 2 dataframe[f"mid_smooth{suffixe}"] = dataframe['mid'] dataframe = self.calculeDerivees(dataframe, f"mid_smooth{suffixe}", timeframe=timeframe, ema_period=window) return dataframe @@ -1049,7 +1184,7 @@ class Zeus_8_3_2_B_4_2(IStrategy): eps_d1_series = eps_d1_series.fillna(global_eps_d1).replace(0, global_eps_d1) eps_d2_series = eps_d2_series.fillna(global_eps_d2).replace(0, global_eps_d2) - if verbose: + if verbose and self.dp.runmode.value in ('backtest'): stats = dataframe[[d1_col, d2_col]].agg(['min', 'max']).T stats['abs_max'] = dataframe[[d1_col, d2_col]].abs().max(axis=0) print(f"---- Derivatives stats {timeframe}----") @@ -1094,79 +1229,13 @@ class Zeus_8_3_2_B_4_2(IStrategy): dataframe[tendency_col] = dataframe.apply(tag_by_derivatives, axis=1) - return dataframe + if timeframe == '1h' and verbose and self.dp.runmode.value in ('backtest'): + print("##################") + print(f"# STAT {timeframe} {name}{suffixe}") + print("##################") + self.calculateProbabilite2Index(dataframe, futur_cols=['futur_percent'], indic_1=f"{name}{suffixe}_deriv1", indic_2=f"{name}{suffixe}_deriv2") - # def compute_derivatives(self, df: pd.DataFrame, col: str, eps: float = 1e-4, smooth: int = 15) -> pd.DataFrame: - # """ - # Ajoute (col)_deriv1, (col)_deriv2, (col)_state avec un lissage (smooth) - # des dérivées pour éviter les sauts / escaliers. - # eps = seuil neutre - # smooth = taille du rolling/ema appliqué sur les dérivées - # """ - # - # d1_col = f"{col}_deriv1" - # d2_col = f"{col}_deriv2" - # d1s_col = f"{col}_deriv1s" - # d2s_col = f"{col}_deriv2s" - # state_col = f"{col}_state" - # - # # dérivée première brute - # df[d1_col] = (df[col] - df[col].shift(1)) / df[col].shift(1) - # # lissage EMA (plus doux qu'un rolling, surtout en trading) - # df[d1_col] = df[d1_col].rolling(smooth).mean() - # - # # dérivée seconde brute - # df[d2_col] = df[d1_col] - df[d1_col].shift(1) - # df[d2_col] = df[d2_col].rolling(smooth).mean() - # - # print("---- Derivatives stats for", col, "----") - # print(df[[d1_col, d2_col]].agg(['min', 'max'])) - # print("---------------------------------------------") - # - # # lissage EMA (plus doux qu'un rolling, surtout en trading) - # # df[d1_col] = df[d1_col].ewm(span=smooth, adjust=False).mean() - # # df[d2_col] = df[d2_col].ewm(span=smooth, adjust=False).mean() - # - # # signe avec epsilon - # def sign_eps(x: float, eps: float) -> int: - # if x > eps: return 1 - # if x < -eps: return -1 - # return 0 - # - # df[d1s_col] = df[d1_col].apply(sign_eps, eps1) - # df[d2s_col] = df[d2_col].apply(sign_eps, eps2) - # - # # mapping état → codes 3 lettres explicites - # # | Ancien état | Nouveau code 3 lettres | Interprétation | - # # | ----------- | ---------------------- | --------------------- | - # # | 1 | HAU | Hausse Accélérée | - # # | 2 | HSR | Hausse Ralentissement | - # # | 3 | HST | Hausse Stable | - # # | 4 | DHB | Départ Hausse | - # # | 5 | PAL | Palier / neutre | - # # | 6 | DBD | Départ Baisse | - # # | 7 | BSR | Baisse Ralentissement | - # # | 8 | BST | Baisse Stable | - # # | 9 | BAS | Baisse Accélérée | - # - # state_map = { - # (1, 1): 4, #"HAU", - # (1, 0): 3, #"HSR", - # (1, -1): 2, # "HST", - # (0, 1): 1, #"DHB", - # (0, 0): 0, #"PAL", - # (0, -1): -1, #"DBD", - # (-1, 1): -2, #"BSR", - # (-1, 0): -3, #"BST", - # (-1, -1): -4, # "BAS", - # } - # - # df[state_col] = list(map( - # lambda x: state_map.get((x[0], x[1]), "MID"), - # zip(df[d1s_col], df[d2s_col]) - # )) - # - # return df + return dataframe def getOpenTrades(self): # if len(self.trades) == 0: @@ -1175,23 +1244,34 @@ class Zeus_8_3_2_B_4_2(IStrategy): def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: pair = metadata['pair'] - dataframe.loc[ - ( - (dataframe['mid_smooth_3'].shift(1) < dataframe['mid_smooth_3']) - & (dataframe['hapercent'] > 0) - & ((dataframe['max_rsi_24_1h'] < 70) | (dataframe['close'] < dataframe['close_1d'])) - & (dataframe['open'] <= dataframe['bb_middleband']) - & (dataframe['sma60_deriv1'] >= 0) - & (dataframe['sma60_deriv2'] >= 0) - ), ['enter_long', 'enter_tag']] = (1, 'smth') + # trend = self.getTrend(dataframe) + # # params = self.loadParamsFor(pair, trend) + # + # indic_5m = self.getParamValue(pair, trend, 'buy', 'indic_5m') + # indic_deriv1_5m = self.getParamValue( pair, trend, 'buy', 'indic_deriv1_5m') + # indic_deriv2_5m = self.getParamValue( pair, trend, 'buy', 'indic_deriv2_5m') + + # dataframe.loc[ + # ( + # (dataframe['mid_smooth_3'].shift(1) < dataframe['mid_smooth_3']) + # & (dataframe['hapercent'] > 0) + # & ((dataframe['max_rsi_24_1h'] < 70) | (dataframe['close'] < dataframe['close_1d'])) + # & (dataframe['open'] <= dataframe['bb_middleband']) + # & (dataframe[f"{self.indic_5m.value}_deriv1"] >= self.indic_deriv1_5m.value) + # & (dataframe[f"{self.indic_5m.value}_deriv2"] >= self.indic_deriv2_5m.value) + # # & (dataframe[f"{indic_1h}_deriv1"] >= self.indic_deriv1_1h.value) + # # & (dataframe[f"{indic_1h}_deriv2"] >= self.indic_deriv2_1h.value) + # ), ['enter_long', 'enter_tag']] = (1, 'smth') dataframe.loc[ ( (dataframe['sma24_deriv2'].shift(1) < 0) & (dataframe['sma24_deriv2'] > 0) & ((dataframe['max_rsi_24_1h'] < 70) | (dataframe['close'] < dataframe['close_1d'])) - & (dataframe['sma60_deriv1'] >= 0) - & (dataframe['sma60_deriv2'] >= 0) + & (dataframe[f"{self.indic_5m.value}_deriv1"] >= self.indic_deriv1_5m.value) + & (dataframe[f"{self.indic_5m.value}_deriv2"] >= self.indic_deriv2_5m.value) + # & (dataframe[f"{indic_1h}_deriv1"] >= self.indic_deriv1_1h.value) + # & (dataframe[f"{indic_1h}_deriv2"] >= self.indic_deriv2_1h.value) ), ['enter_long', 'enter_tag']] = (1, 'invert') dataframe.loc[ @@ -1200,29 +1280,34 @@ class Zeus_8_3_2_B_4_2(IStrategy): & (dataframe['sma60_deriv1'].shift(1) < 0) & (dataframe['sma60_deriv1'] > 0) & ((dataframe['max_rsi_24_1h'] < 70) | (dataframe['close'] < dataframe['close_1d'])) - & (dataframe['sma60_deriv1'] >= 0) - & (dataframe['sma60_deriv2'] >= 0) + & (dataframe[f"{self.indic_5m.value}_deriv1"] >= self.indic_deriv1_5m.value) + & (dataframe[f"{self.indic_5m.value}_deriv2"] >= self.indic_deriv2_5m.value) + # & (dataframe[f"{indic_1h}_deriv1"] >= self.indic_deriv1_1h.value) + # & (dataframe[f"{indic_1h}_deriv2"] >= self.indic_deriv2_1h.value) ), ['enter_long', 'enter_tag']] = (1, 'raise') - dataframe.loc[ - ( - (dataframe['sma60_deriv1'].shift(1) < 0) - & (dataframe['sma24_deriv2'] > 0) - & ((dataframe['max_rsi_24_1h'] < 70) | (dataframe['close'] < dataframe['close_1d'])) - & (dataframe['sma60_deriv1'] >= 0) - & (dataframe['sma60_deriv2'] >= 0) - ), ['enter_long', 'enter_tag']] = (1, 'stg_inv') + # dataframe.loc[ + # ( + # (dataframe['sma60_deriv1'].shift(1) < 0) + # & (dataframe['sma24_deriv2'] > 0) + # & ((dataframe['max_rsi_24_1h'] < 70) | (dataframe['close'] < dataframe['close_1d'])) + # & (dataframe[f"{self.indic_5m.value}_deriv1"] >= self.indic_deriv1_5m.value) + # & (dataframe[f"{self.indic_5m.value}_deriv2"] >= self.indic_deriv2_5m.value) + # # & (dataframe[f"{indic_1h}_deriv1"] >= self.indic_deriv1_1h.value) + # # & (dataframe[f"{indic_1h}_deriv2"] >= self.indic_deriv2_1h.value) + # ), ['enter_long', 'enter_tag']] = (1, 'stg_inv') dataframe.loc[ ( (dataframe['mid_smooth_3_1h'].shift(24) >= dataframe['mid_smooth_3_1h'].shift(12)) & (dataframe['mid_smooth_3_1h'].shift(12) <= dataframe['mid_smooth_3_1h']) & ((dataframe['max_rsi_24_1h'] < 70) | (dataframe['close'] < dataframe['close_1d'])) - & (dataframe['sma60_deriv1'] >= 0) - & (dataframe['sma60_deriv2'] >= 0) + & (dataframe[f"{self.indic_5m.value}_deriv1"] >= self.indic_deriv1_5m.value) + & (dataframe[f"{self.indic_5m.value}_deriv2"] >= self.indic_deriv2_5m.value) + # & (dataframe[f"{indic_1h}_deriv1"] >= self.indic_deriv1_1h.value) + # & (dataframe[f"{indic_1h}_deriv2"] >= self.indic_deriv2_1h.value) ), ['enter_long', 'enter_tag']] = (1, 'smth3_inv') - dataframe['test'] = np.where(dataframe['enter_long'] == 1, dataframe['close'] * 1.01, np.nan) if self.dp.runmode.value in ('backtest'): @@ -1290,6 +1375,89 @@ class Zeus_8_3_2_B_4_2(IStrategy): print(f"'{index}': [{row_values}], ") print("}") + data = {} + for index, row in df_formatted.iterrows(): + # on convertit proprement avec arrondi comme dans ton print, mais en données réelles + data[index] = [ + None if (isinstance(val, float) and math.isnan(val)) else val + for val in row + ] + + # Niveaux unicode pour les barres verticales (style sparkline) + # spark_chars = "▁▂▃▄▅▆▇█" + + # print(data.values()) + # # Collecte globale min/max + # all_values = [] + # for vals in data.values(): + # all_values.extend(v for v in vals if not (isinstance(v, float) and math.isnan(v))) + # + # global_min = min(all_values) if all_values else 0 + # global_max = max(all_values) if all_values else 1 + # global_span = (global_max - global_min) if global_max != global_min else 1 + # + # def sparkline_global(values): + # if all(isinstance(v, float) and math.isnan(v) for v in values): + # return "(no data)" + # out = "" + # for v in values: + # if isinstance(v, float) and math.isnan(v): + # out += " " + # else: + # idx = int((v - global_min) / global_span * (len(spark_chars) - 1)) + # out += spark_chars[idx] + # return out + # + # for key, values in data.items(): + # print(f"{key:>3} : {sparkline_global(values)}") + + # Palette ANSI 256 couleurs pour heatmap + def get_ansi_color(val): + """ + Échelle fixe 0→100 : + 0-20 : bleu (21) + 20-40 : cyan (51) + 40-60 : vert/jaune (46 / 226) + 60-80 : orange (208) + 80-100 : rouge (196) + """ + if val is None: + return "" + if val < 0: + val = 0 + elif val > 100: + val = 100 + + if val <= 20: + code = 21 + elif val <= 40: + code = 51 + elif val <= 60: + code = 226 + elif val <= 80: + code = 208 + else: + code = 196 + return f"\033[38;5;{code}m" + + RESET = "\033[0m" + + # Affichage + columns = ['B3', 'B2', 'B1', 'N0', 'H1', 'H2', 'H3'] + header = " " + " ".join([f"{col:>6}" for col in columns]) + print(header) + print("-" * len(header)) + + for key, values in data.items(): + line = f"{key:>3} |" + for v in values: + if v is None: + line += f" {' '} " # vide pour NaN / None + else: + color = get_ansi_color(v) + line += f" {color}{v:5.1f}{RESET} " + print(line) + def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: # dataframe.loc[ # ( @@ -1328,6 +1496,7 @@ class Zeus_8_3_2_B_4_2(IStrategy): days_since_open = (current_time_utc - open_date).days pair = trade.pair profit = round(current_profit * trade.stake_amount, 1) + last_lost = self.getLastLost(last_candle, pair) pct_first = 0 total_counts = sum( @@ -1336,7 +1505,7 @@ class Zeus_8_3_2_B_4_2(IStrategy): if self.pairs[pair]['first_buy']: pct_first = self.getPctFirstBuy(pair, last_candle) - pct = 0.012 + pct = self.pct.value if count_of_buys == 1: pct_max = current_profit else: @@ -1346,7 +1515,7 @@ class Zeus_8_3_2_B_4_2(IStrategy): pct_max = - pct if (self.getShortName(pair) == 'BTC') or count_of_buys <= 2: - lim = - pct - (count_of_buys * 0.001) + lim = - pct - (count_of_buys * self.pct_inc.value) # lim = self.getLimitBuy(pair, last_candle, pct) # lim = - (0.012 * (1 + round(count_of_buys / 5)) + 0.001 * (count_of_buys - 1)) # lim = - (0.012 + 0.001 * (count_of_buys - 1) + (0.002 * count_of_buys if count_of_buys > 10 else 0.001 * count_of_buys if count_of_buys > 5 else 0)) @@ -1363,123 +1532,10 @@ class Zeus_8_3_2_B_4_2(IStrategy): if not self.should_enter_trade(pair, last_candle, current_time): return None - # if self.dp.runmode.value in ('dry_run'): - # if pair not in ('BTC/USDT', 'BTC/USDC', 'XRP/USDT', 'XRP/USDC', 'ETH/USDT', 'ETH/USDC', 'SOL/USDT', 'SOL/USDT'): - # # print(f"skip pair {pair}") - # return None + condition = (last_candle['enter_long'] and last_candle['sma5_deriv1_1h'] > 0) or (last_candle['percent3'] < -0.03 and last_candle['percent'] > 0) - # else: - # if pair not in ('BTC/USDT', 'BTC/USDC'): - # btc_count = self.pairs['BTC/USDT']['count_of_buys'] + self.pairs['BTC/USDC']['count_of_buys'] - # # print(f"skip pair {pair}") - # if (btc_count > 4 or count_of_buys + 1 > btc_count) and pct_max < 0.20: - # return None - - # # déclenche un achat si bougie rouge importante - # stake_amount = self.config.get('stake_amount') - # stake_amount = min(stake_amount, self.wallets.get_available_stake_amount()) - # current_time = current_time.astimezone(timezone.utc) - # seconds_since_filled = (current_time - trade.date_last_filled_utc).total_seconds() - # pct = (last_candle['close'] - last_candle['open']) / (last_candle['open']) * 100 - # if ( - # stake_amount - # and pct <= - 1.10 #self.red_candle_pct - # and min_stake < stake_amount < max_stake - # and seconds_since_filled > (60 * 5) - # # and (last_candle["sma24_deriv1_1h"] > - 0.02) - # # and seconds_since_filled > (1 * 3600) - # # and count_of_entries < 10 - # ): - # trade_type = last_candle['enter_tag'] if last_candle['enter_long'] == 1 else 'pct48' - # self.log_trade( - # last_candle=last_candle, - # date=current_time, - # action="Adjust 1", - # dispo=dispo, - # pair=trade.pair, - # rate=current_rate, - # trade_type=trade_type, - # profit=round(current_profit, 4), # round(current_profit * trade.stake_amount, 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 - # - # # déclenche un achat en conditions d'achat standard - # if ( - # stake_amount - # and last_candle['close'] < last_candle['sma24'] - # and last_candle['close'] < last_candle['open'] - # and min_stake < stake_amount < max_stake - # and (last_candle["sma24_deriv1_1h"] > - 0.02) - # and seconds_since_filled > 23 * 3600 #self.staking_delay * 3600 - # ): - # stake_amount = stake_amount * seconds_since_filled / (23 * 3600) - # trade_type = last_candle['enter_tag'] if last_candle['enter_long'] == 1 else 'pct48' - # self.log_trade( - # last_candle=last_candle, - # date=current_time, - # action="Adjust 2", - # dispo=dispo, - # pair=trade.pair, - # rate=current_rate, - # trade_type=trade_type, - # profit=round(current_profit, 4), # round(current_profit * trade.stake_amount, 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 - # - # return None - - # index = self.get_palier_index(pct_first) - # if index is None: - # return None - # index = index -1 - # lim, stake_amount = self.paliers[index] #- pct - (count_of_buys * 0.001) - - # self.get_active_stake() - # val144 = self.getProbaHausse144(last_candle) - # val1h = self.getProbaHausse1h(last_candle) - # val = self.getProbaHausse(last_candle) - - # buy = False - # previous = 0 - # # current_profit=-0.001998 count_of_buys=1 pct_first=0.000 pct_palier=-0.629 pct_max=-0.002 lim=0.000 - # - # for pct_palier, stake_amount in self.paliers: - # if abs(pct_palier) > abs(pct_first): - # lim = pct_palier - # break - # previous = pct_palier - - # print(f"{trade.pair} current_profit={current_profit} count_of_buys={count_of_buys} pct_first={pct_first:.3f} pct_palier={pct_palier:.3f} pct_max={pct_max:.3f} lim={lim:.3f} ") - - # if (days_since_open > count_of_buys) & (0 < count_of_buys <= max_buys) & (current_rate <= limit) & (last_candle['enter_long'] == 1): - - # ['mid_smooth_1h_deriv1'] - # sans cdt° ==>Avg. stake amount 276.516 USDT │ Total trade volume 175760.342 USDT 315 │ 1.17 │ 1204.156 │ 60.21│ 1 day, 13:45:00 │ 314 0 1 99.7 │ 0.787 USDT 0.02% │ - # > - 0.03 ==>Avg. stake amount 259.702 USDT │ Total trade volume 149302.88 USDT 285 │ 1.19 │ 974.542 │ 48.73│ 1 day, 17:45:00 │ 284 0 1 99.6 │ 0.787 USDT 0.03% │ - # > - 0.03 ==>Avg. stake amount 253.535 USDT │ Total trade volume 145312.936 USDT 284 │ 1.19 │ 1014.898 │ 50.74| 1 day, 17:54:00 │ 283 0 1 99.6 │ 0.684 USDT 0.02% │ - # > - 0.015 ==>Avg. stake amount 249.107 USDT │ Total trade volume 138186.861 USDT 275 │ 1.20 │ 901.976 │ 45.1 │ 1 day, 19:17:00 │ 274 0 1 99.6 │ 0.684 USDT 0.02% - - condition = (last_candle['sma24_deriv1'] > 0) # and \ - if (self.getShortName(pair) != 'BTC' and count_of_buys > 3): - condition = before_last_candle_24['mid_smooth_3_1h'] > before_last_candle_12['mid_smooth_3_1h'] and before_last_candle_12['mid_smooth_3_1h'] < last_candle['mid_smooth_3_1h'] #and last_candle['mid_smooth_3_deriv1_1h'] < -1.5 - - # (last_candle['mid_smooth_1h_deriv1'] > 0 and last_candle['mid_smooth_1h_deriv1']) - # last_candle['mid_smooth_1h_deriv1'] > - 0.05 #(last_candle['mid_smooth_3_deriv1'] > self.buy_mid_smooth_3_deriv1.value) and (last_candle['mid_smooth_24_deriv1'] > self.buy_mid_smooth_24_deriv1.value) - # (last_candle['enter_long'] == 1 & (count_of_buys < 3)) \ - # or ((before_last_candle['mid_re_smooth_3_deriv1'] <= 0) & (last_candle['mid_re_smooth_3_deriv1'] >= 0) & (3 <= count_of_buys < 6)) \ - # or ((before_last_candle['mid_smooth_1h_deriv1'] <= 0) & (last_candle['mid_smooth_1h_deriv1'] >= 0) & (6 <= count_of_buys)) + # if (self.getShortName(pair) != 'BTC' and count_of_buys > 3): + # condition = before_last_candle_24['mid_smooth_3_1h'] > before_last_candle_12['mid_smooth_3_1h'] and before_last_candle_12['mid_smooth_3_1h'] < last_candle['mid_smooth_3_1h'] #and last_candle['mid_smooth_3_deriv1_1h'] < -1.5 limit_buy = 40 if (count_of_buys < limit_buy) and condition and (pct_max < lim): @@ -1504,13 +1560,12 @@ class Zeus_8_3_2_B_4_2(IStrategy): # # if count < 3: # return None - last_lost = self.getLastLost(last_candle, pair) max_amount = self.config.get('stake_amount') * 2.5 # stake_amount = min(stake_amount, self.wallets.get_available_stake_amount()) stake_amount = min(min(max_amount, self.wallets.get_available_stake_amount()), self.adjust_stake_amount(pair, - last_candle) - 10 * pct_first / self.mise_factor_buy.value) # min(200, self.adjust_stake_amount(pair, last_candle) * self.fibo[count_of_buys]) + last_candle) - 10 * last_lost / self.mise_factor_buy.value) # min(200, self.adjust_stake_amount(pair, last_candle) * self.fibo[count_of_buys]) if self.wallets.get_available_stake_amount() > stake_amount: trade_type = last_candle['enter_tag'] if last_candle['enter_long'] == 1 else 'pct48' @@ -1544,35 +1599,14 @@ class Zeus_8_3_2_B_4_2(IStrategy): return stake_amount return None except Exception as exception: - # print(exception) + print(exception) return None - # if (count_of_buys >= 6): - # self.log_trade( - # last_candle=last_candle, - # date=current_time, - # action="Sell", - # dispo=dispo, - # pair=trade.pair, - # rate=current_rate, - # trade_type="Stop loss", - # profit=round(current_profit, 4), # round(current_profit * trade.stake_amount, 1), - # buys=trade.nr_of_successful_entries + 1, - # stake=-trade.stake_amount - # ) - # 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 -trade.stake_amount - last_lost = self.getLastLost(last_candle, pair) - - if (profit > self.pairs[pair]['previous_profit'] and profit > self.pairs[pair]['expected_profit'] and hours > 6 + if (profit > self.pairs[pair]['previous_profit'] and profit > self.pairs[pair]['expected_profit'] and hours > 6 # and last_candle['sma60_deriv1'] > 0 and last_candle['max_rsi_12_1h'] < 75 - # and last_candle['rsi_deriv1_1h'] > 0 + and last_candle['rsi_1d'] < 58 # and last_candle['mid_smooth_5_deriv1_1d'] > 0 - and last_candle['sma60_deriv1'] > 0 - and last_candle['sma60_deriv2'] > 0 ): try: self.pairs[pair]['previous_profit'] = profit @@ -1601,7 +1635,7 @@ class Zeus_8_3_2_B_4_2(IStrategy): return stake_amount return None except Exception as exception: - # print(exception) + print(exception) return None return None @@ -1663,7 +1697,9 @@ class Zeus_8_3_2_B_4_2(IStrategy): def adjust_stake_amount(self, pair: str, last_candle: DataFrame): # Calculer le minimum des 14 derniers jours - base_stake_amount = self.config.get('stake_amount') # Montant de base configuré + nb_pairs = len(self.dp.current_whitelist()) + + base_stake_amount = self.config.get('stake_amount') #/ (self.mises.value * nb_pairs) # Montant de base configuré # pct60 = round(100 * self.getPctClose60D(pair, last_candle), 2) if True: # not pair in ('BTC/USDT', 'BTC/USDC'): @@ -1672,9 +1708,9 @@ class Zeus_8_3_2_B_4_2(IStrategy): # pctClose60 = self.getPctClose60D(pair, last_candle) # dist_max = self.getDistMax(last_candle, pair) # factor = self.multi_step_interpolate(dist_max, self.thresholds, self.factors) - factor = 0.5 - if last_candle['sma60_deriv1'] > 0 and last_candle['sma24_deriv1_1h'] > 0: - factor = 1.5 + factor = 1 #65 / min(65, last_candle['rsi_1d']) + # if last_candle['sma60_deriv1'] > 0 and last_candle['sma24_deriv1_1h'] > 0: + # factor = 1.5 adjusted_stake_amount = max(base_stake_amount / 5, base_stake_amount * factor) else: @@ -1759,10 +1795,7 @@ class Zeus_8_3_2_B_4_2(IStrategy): # if last_candle['close'] < max_60: # pct_to_max = 0.25 * (max_60 - last_candle['close']) / max_60 # pct_to_max = pct_to_max * (2 - pctClose60) - 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 + 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 @@ -2006,18 +2039,6 @@ class Zeus_8_3_2_B_4_2(IStrategy): smoothed.append(last) return pd.Series(smoothed, index=series.index) - def smooth_and_derivatives(self, series, window=25, polyorder=3): - series = series.copy() - if series.isna().sum() > 0: - series = series.ffill().bfill() # Si tu veux éviter toute NaN - - smooth = self.causal_savgol(series, window=window, polyorder=polyorder) - deriv1 = np.diff(smooth, prepend=smooth[0]) - deriv2 = np.diff(deriv1, prepend=deriv1[0]) - - return pd.Series(smooth, index=series.index), pd.Series(deriv1, index=series.index), pd.Series(deriv2, - index=series.index) - def causal_savgol(self, series, window=25, polyorder=3): result = [] half_window = window # Fenêtre complète dans le passé @@ -2280,18 +2301,10 @@ class Zeus_8_3_2_B_4_2(IStrategy): # print(pivot_mean.round(2)) def should_enter_trade(self, pair: str, last_candle, current_time) -> bool: - return True limit = 3 - # 🟢 Dérivée 1 > 0 et dérivée 2 > 0: tendance haussière qui s’accélère. - # 🟡 Dérivée 1 > 0 et dérivée 2 < 0: tendance haussière qui ralentit → essoufflement potentiel. - # 🔴 Dérivée 1 < 0 et dérivée 2 < 0: tendance baissière qui s’accélère. - # 🟠 Dérivée 1 < 0 et dérivée 2 > 0: tendance baissière qui ralentit → possible bottom. - - # if not pair.startswith('BTC'): - dispo = round(self.wallets.get_available_stake_amount()) - - if self.pairs[pair]['stop'] and (last_candle['sma60_deriv1'] > 0.0 and last_candle['sma60_deriv2'] > 0): + if self.pairs[pair]['stop'] and last_candle['max_rsi_12_1h'] <= 60 and last_candle['trend_class_1h'] == 'BR1': + dispo = round(self.wallets.get_available_stake_amount()) self.pairs[pair]['stop'] = False self.log_trade( last_candle=last_candle, @@ -2305,26 +2318,55 @@ class Zeus_8_3_2_B_4_2(IStrategy): buys=self.pairs[pair]['count_of_buys'], stake=0 ) - else: - if self.pairs[pair]['stop'] == False and (last_candle['sma60_deriv1'] < -0.01 and last_candle['sma60_deriv2'] < 0): - self.pairs[pair]['stop'] = True - # if self.pairs[pair]['current_profit'] > 0: - # self.pairs[pair]['force_sell'] = True - self.log_trade( - last_candle=last_candle, - date=current_time, - action="🔴STOP", - dispo=dispo, - pair=pair, - rate=last_candle['close'], - trade_type='', - profit=self.pairs[pair]['current_profit'], - buys=self.pairs[pair]['count_of_buys'], - stake=0 - ) - return False - if self.pairs[pair]['stop']: - return False + + # 🟢 Dérivée 1 > 0 et dérivée 2 > 0: tendance haussière qui s’accélère. + # 🟡 Dérivée 1 > 0 et dérivée 2 < 0: tendance haussière qui ralentit → essoufflement potentiel. + # 🔴 Dérivée 1 < 0 et dérivée 2 < 0: tendance baissière qui s’accélère. + # 🟠 Dérivée 1 < 0 et dérivée 2 > 0: tendance baissière qui ralentit → possible bottom. + + # if not pair.startswith('BTC'): + dispo = round(self.wallets.get_available_stake_amount()) + + # if self.pairs[pair]['stop'] \ + # and last_candle[f"{self.indic_1d_p.value}_deriv1_1h"] >= self.indic_deriv1_1d_p_start.value \ + # and last_candle[f"{self.indic_1d_p.value}_deriv2_1h"] >= self.indic_deriv2_1d_p_start.value: + # self.pairs[pair]['stop'] = False + # self.log_trade( + # last_candle=last_candle, + # date=current_time, + # action="🟢RESTART", + # dispo=dispo, + # pair=pair, + # rate=last_candle['close'], + # trade_type='', + # profit=0, + # buys=self.pairs[pair]['count_of_buys'], + # stake=0 + # ) + # else: + # if self.pairs[pair]['stop'] == False \ + # and last_candle[f"{self.indic_1d_p.value}_deriv1_1h"] <= self.indic_deriv1_1d_p_stop.value \ + # and last_candle[f"{self.indic_1d_p.value}_deriv2_1h"] <= self.indic_deriv2_1d_p_stop.value: + # self.pairs[pair]['stop'] = True + # # if self.pairs[pair]['current_profit'] > 0: + # # self.pairs[pair]['force_sell'] = True + # self.log_trade( + # last_candle=last_candle, + # date=current_time, + # action="🔴STOP", + # dispo=dispo, + # pair=pair, + # rate=last_candle['close'], + # trade_type='', + # profit=self.pairs[pair]['current_profit'], + # buys=self.pairs[pair]['count_of_buys'], + # stake=0 + # ) + # return False + # if self.pairs[pair]['stop']: + # return False + + return True # if last_candle['sma5_deriv1_1h'] < -0.02: # return False @@ -2410,48 +2452,201 @@ class Zeus_8_3_2_B_4_2(IStrategy): else: return True - def calculePlateaux(self, informative: pd.DataFrame, plateau_duration, plateau_tolerance) -> pd.DataFrame: + @staticmethod + def check_derivatives_vectorized(dataframe, deriv_pairs, thresholds): + """ + Retourne True si toutes les dérivées respectent leur seuil. + """ + mask = pd.Series(True, index=dataframe.index) + for d1_col, d2_col in deriv_pairs: + d1_thresh = thresholds.get(d1_col, 0) + d2_thresh = thresholds.get(d2_col, 0) + mask &= (dataframe[d1_col] >= d1_thresh) & (dataframe[d2_col] >= d2_thresh) + return mask - # 1. Détection plateau - informative['rolling_min'] = informative['close'].rolling(plateau_duration).min() - informative['rolling_max'] = informative['close'].rolling(plateau_duration).max() - informative['plateau_amplitude'] = (informative['rolling_max'] - informative['rolling_min']) / informative[ - 'rolling_min'] - informative['plateau'] = informative['plateau_amplitude'] < plateau_tolerance + # ---------------------------------------------------------------------------------------------- + # fallback defaults (used when no JSON exists) + PARAMS_DIR = 'params' - # 2. Détection "fin de plateau" - # informative['plateau_end'] = (informative['plateau'] & ~informative['plateau'].shift(-1).fillna(False).astype(bool)) - next_plateau = informative['plateau'].shift(-1) - next_plateau = next_plateau.fillna(False).astype(bool) - informative['plateau_end'] = informative['plateau'] & ~next_plateau + DEFAULT_PARAMS = { + "rsi_buy": 30, + "rsi_sell": 70, + "ema_period": 21, + "sma_short": 20, + "sma_long": 100, + "atr_period": 14, + "atr_multiplier": 1.5, + "stake_amount": None, # use exchange default + "stoploss": -0.10, + "minimal_roi": {"0": 0.10} + } - # 3. Enregistrer dernier plateau (min/max) - last_min = None - last_max = None - last_status = [] + def __init__(self, config: dict) -> None: + super().__init__(config) + self.parameters = self.load_params_tree("user_data/strategies/params/") - for i, row in informative.iterrows(): - if row['plateau_end']: - last_min = row['rolling_min'] - last_max = row['rolling_max'] + def setTrends(self, dataframe: DataFrame): + SMOOTH_WIN=10 + df = dataframe.copy() - if last_min is not None and last_max is not None: - if row['close'] > last_max: - breakout = "up" - distance = (row['close'] - last_max) / last_max - elif row['close'] < last_min: - breakout = "down" - distance = (last_min - row['close']) / last_min - else: - breakout = "inside" - distance = 0 + # # --- charger les données --- + # df['timestamp'] = pd.to_datetime(df['timestamp'], errors='coerce') + + # --- calcul SMA14 --- + # df['sma'] = talib.SMA(df, timeperiod=20) # ta.trend.sma_indicator(df['close'], 14) + + # --- pente brute --- + df['slope'] = df['sma12'].diff() + + # --- lissage EMA --- + df['slope_smooth'] = df['slope'].ewm(span=SMOOTH_WIN, adjust=False).mean() + + # df["slope_smooth"] = savgol_filter(df["slope_smooth"], window_length=21, polyorder=3) + + # --- normalisation relative --- + df['slope_norm'] = df['slope_smooth'] / df['close'] + # df['slope_norm'].fillna(0, inplace=True) + df['slope_norm'] = df['slope_norm'].fillna(0) + + # --- classification dynamique via quantiles --- + + q = df['slope_norm'].quantile([0.125, 0.375, 0.625, 0.875]).values + q1, q2, q3, q4 = q + + def classify(v): + if v <= q1: + return 'BR2' + elif v <= q2: + return 'BR1' + elif v <= q3: + return 'RG' + elif v <= q4: + return 'BU1' else: - breakout = None - distance = None + return 'BU2' - last_status.append((breakout, distance)) + dataframe['slope_norm'] = df['slope_norm'] + dataframe['trend_class'] = df['slope_norm'].apply(classify) - informative['breakout_status'] = [s[0] for s in last_status] - informative['breakout_distance'] = [s[1] for s in last_status] + # # -------------------------- Trend detection (M2) -------------------------- + # def getTrend(self, dataframe: DataFrame) -> str: + # """ + # M2: SMA50 / SMA200 golden/death cross + # - bull: sma50 > sma200 + # - bear: sma50 < sma200 + # - range: sma50 ~= sma200 (within a small pct) + # + # Uses only past data (no future lookahead). + # """ + # if dataframe is None or len(dataframe) < max(self.DEFAULT_PARAMS['sma_short'], self.DEFAULT_PARAMS['sma_long']) + 2: + # return 'RANGE' + # + # sma_short = dataframe['close'].rolling(window=self.DEFAULT_PARAMS['sma_short']).mean() + # sma_long = dataframe['close'].rolling(window=self.DEFAULT_PARAMS['sma_long']).mean() + # + # cur_short = sma_short.iloc[-1] + # cur_long = sma_long.iloc[-1] + # + # # small relative threshold to avoid constant flips + # if cur_long == 0 or cur_short == 0: + # return 'RANGE' + # + # rel = abs(cur_short - cur_long) / cur_long + # threshold = 0.01 # 1% by default; tweak as needed + # + # if rel <= threshold: + # return 'RANGE' + # if cur_short > cur_long: + # return 'BULL' + # return 'BEAR' - return informative + # # -------------------------- Parameter loading -------------------------- + # def loadParamsFor(self, pair: str, trend: str) -> dict: + # """Load JSON from params//.json with fallback to DEFAULT_PARAMS.""" + # pair_safe = pair.replace('/', '-') # folder name convention: BTC-USDT + # # cache key + # cache_key = f"{pair_safe}:{trend}" + # if cache_key in self._params_cache: + # return self._params_cache[cache_key] + # + # path = os.path.join(self.PARAMS_DIR, pair_safe, f"{trend}.json") + # if os.path.isfile(path): + # try: + # with open(path, 'r') as f: + # params = json.load(f) + # # merge with defaults so missing keys won't break + # merged = {**self.DEFAULT_PARAMS, **params} + # self._params_cache[cache_key] = merged + # logger.info(f"Loaded params for {pair} {trend} from {path}") + # return merged + # except Exception as e: + # logger.exception(f"Failed to load params {path}: {e}") + # + # # fallback + # logger.info(f"Using DEFAULT_PARAMS for {pair} {trend}") + # self._params_cache[cache_key] = dict(self.DEFAULT_PARAMS) + # return self._params_cache[cache_key] + + + def load_params_tree(self, base_path="user_data/strategies/params/"): + base = Path(base_path) + params_tree = {} + if not base.exists(): + raise FileNotFoundError(f"Base path '{base_path}' not found.") + + for pair_dir in base.iterdir(): + if not pair_dir.is_dir(): + continue + pair = self.getShortName(pair_dir.name) # ex : BTC-USDT + params_tree.setdefault(pair, {}) + + for trend_dir in pair_dir.iterdir(): + if not trend_dir.is_dir(): + continue + trend = trend_dir.name # ex : bull / bear / range + params_tree[pair].setdefault(trend, []) + + for file in trend_dir.glob("*-hyperopt_result.json"): + filename = file.name + + # Extraire START et END + try: + prefix = filename.replace("-hyperopt_result.json", "") + start, end = prefix.split("-", 1) # split en 2 + except Exception: + start = None + end = None + + # Lire le JSON + try: + with open(file, "r") as f: + content = json.load(f) + except Exception as err: + content = {"error": str(err)} + + params_tree[pair][trend].append({ + "start": start, + "end": end, + "file": str(file), + "content": content, + }) + for pair, trends in params_tree.items(): + for trend, entries in trends.items(): + if entries: + # indic_5m = self.getParamValue(pair, trend, 'buy', 'indic_5m') + # indic_deriv1_5m = self.getParamValue(pair, trend, 'buy', 'indic_deriv1_5m') + # indic_deriv2_5m = self.getParamValue(pair, trend, 'buy', 'indic_deriv2_5m') + # + # indic_5m_sell = self.getParamValue(pair, trend, 'sell', 'indic_5m_sell') + # indic_deriv1_5m_sell = self.getParamValue(pair, trend, 'sell', 'indic_deriv1_5m_sell') + # indic_deriv2_5m_sell = self.getParamValue(pair, trend, 'sell', 'indic_deriv2_5m_sell') + + print(f"{pair} -> {trend}") # {indic_5m} {indic_deriv1_5m} {indic_deriv2_5m} {indic_5m_sell} {indic_deriv1_5m_sell} {indic_deriv2_5m_sell}") + # for entry in entries: + # print(entry) + + return params_tree + + def getParamValue(self, pair, trend, space, param): + pair = self.getShortName(pair) + return self.parameters[pair][trend][0]['content']['params'][space][param] \ No newline at end of file