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