]> git.digitality.be Git - pdw25-26/commitdiff
Implémentation amélioration des stratégies et ajout de la stratégie SAFE
authorSacheat <sacha.libion@outlook.com>
Sun, 1 Mar 2026 21:11:42 +0000 (22:11 +0100)
committerSacheat <sacha.libion@outlook.com>
Sun, 1 Mar 2026 21:11:42 +0000 (22:11 +0100)
Wallette/server/modules/strategy/factories/StrategyFactory.js
Wallette/server/modules/strategy/services/BotService.js
Wallette/server/modules/strategy/strategies/GreedyStrategy.js
Wallette/server/modules/strategy/strategies/SafeStrategy.js [new file with mode: 0644]
Wallette/server/modules/strategy/strategies/StandardStrategy.js

index 21c8b678882fe7fd6e328104d75e1ec290aba1f9..efbf5ede06f8480ac8e53ee948e62b8c8ae9eb7b 100644 (file)
@@ -1,12 +1,14 @@
 import { StrategyProfile } from '../config/enum.js';
 import StandardStrategy from '../strategies/StandardStrategy.js';
 import GreedyStrategy from '../strategies/GreedyStrategy.js';
+import SafeStrategy from "../strategies/SafeStrategy.js";
 
 class StrategyFactory {
 
     static getStrategy(mode) {
         switch (mode) {
             case StrategyProfile.SAFE:
+                return new SafeStrategy();
             case StrategyProfile.STANDARD:
                 return new StandardStrategy();
 
index d1b1f2e672d0c825b04355e6a1d228bd58062306..d1fa9b9945b0e24e6f9cd4c0a0e3193c103611d4 100644 (file)
@@ -84,7 +84,7 @@ class BotService {
 
     async sendToAlertsModule(signalPayload) {
         try {
-            
+
             const BROADCAST_URL = 'http://127.0.0.1:3003/test/broadcast-alert';
 
             const response = await fetch(
@@ -92,7 +92,7 @@ class BotService {
                 {
                     method: 'POST',
                     headers: { 'Content-Type': 'application/json' },
-                    body: JSON.stringify(signalPayload)
+                    body: JSON.stringify({alert: signalPayload})
                 }
             );
 
index 7aeabc6e1976c34f109821e9303c76c14ba21244..d0d75ab81e3a71c9b0bb76dcf171c9d83d4847ea 100644 (file)
@@ -1,6 +1,6 @@
 import TradingStrategy from './TradingStrategy.js';
 import { ActionTypes } from '../config/enum.js';
-import { EMA } from 'technicalindicators';
+import { EMA, BollingerBands, SMA, ATR, RSI } from 'technicalindicators';
 
 class GreedyStrategy extends TradingStrategy {
     constructor() {
@@ -8,38 +8,105 @@ class GreedyStrategy extends TradingStrategy {
     }
 
     analyze(candles, userParams) {
-        const prices = candles.map(c => parseFloat(c.close_price));
+        // 1. Extraction des données (OHLCV)
+        const closes = candles.map(c => parseFloat(c.close_price));
+        const highs = candles.map(c => parseFloat(c.high_price));
+        const lows = candles.map(c => parseFloat(c.low_price));
+        const volumes = candles.map(c => parseFloat(c.volume || 0));
 
-        // On récupère les paramètres de la DB
-        const shortPeriod = userParams?.ema_short || 9;
-        const longPeriod = userParams?.ema_long || 21;
+        // 2. Paramètres agressifs de la stratégie
+        const emaShortPeriod = userParams?.ema_short || 9;
+        const emaLongPeriod = userParams?.ema_long || 21;
+        const bbPeriod = userParams?.bb_period || 20;
+        const bbStdDev = userParams?.bb_stddev || 2;
+        const atrPeriod = userParams?.atr_period || 14;
+        const volPeriod = 20; // Période fixe pour moyenner le volume
 
-        // Sécurité : A-t-on assez de bougies ?
-        if (prices.length < longPeriod) {
-            console.log(`Pas assez de données pour ${this.name} (Requis: ${longPeriod}, Reçu: ${prices.length})`);
+        // Sécurité : A-t-on assez de données ? (Au moins 21 bougies pour l'EMA longue)
+        const minDataRequired = Math.max(emaLongPeriod, bbPeriod, atrPeriod);
+        if (closes.length < minDataRequired) {
+            console.log(`[GREEDY] Pas assez de données. Requis: ${minDataRequired}, Reçu: ${closes.length}`);
             return null;
         }
 
-        // Calcul des deux courbes EMA
-        const emaShortValues = EMA.calculate({ period: shortPeriod, values: prices });
-        const emaLongValues = EMA.calculate({ period: longPeriod, values: prices });
+        // 3. Calculs des indicateurs
+        const emaShortVals = EMA.calculate({ period: emaShortPeriod, values: closes });
+        const emaLongVals = EMA.calculate({ period: emaLongPeriod, values: closes });
+        const bbVals = BollingerBands.calculate({ period: bbPeriod, stdDev: bbStdDev, values: closes });
+        const volSmaVals = SMA.calculate({ period: volPeriod, values: volumes });
+        const atrVals = ATR.calculate({ high: highs, low: lows, close: closes, period: atrPeriod });
+        const rsiVals = RSI.calculate({ period: 14, values: closes });
 
-        const lastShort = emaShortValues[emaShortValues.length - 1];
-        const lastLong = emaLongValues[emaLongValues.length - 1];
-        const currentPrice = prices[prices.length - 1];
+        // Récupération des dernières valeurs
+        const currentPrice = closes[closes.length - 1];
+        const currentEmaShort = emaShortVals[emaShortVals.length - 1];
+        const currentEmaLong = emaLongVals[emaLongVals.length - 1];
+        const currentBB = bbVals[bbVals.length - 1];
+        const currentVolume = volumes[volumes.length - 1];
+        const currentVolSMA = volSmaVals[volSmaVals.length - 1];
+        const currentATR = atrVals[atrVals.length - 1];
+        const currentRSI = rsiVals[rsiVals.length - 1];
 
-        console.log(`Analyse ${this.name} -> Prix: ${currentPrice} | EMA Courte(${shortPeriod}): ${lastShort.toFixed(2)} | EMA Longe(${longPeriod}): ${lastLong.toFixed(2)}`);
+        // Objet d'indicateurs pour la DB
+        const currentIndicators = {
+            ema_short: parseFloat(currentEmaShort.toFixed(2)),
+            ema_long: parseFloat(currentEmaLong.toFixed(2)),
+            bb_upper: parseFloat(currentBB.upper.toFixed(2)),
+            vol_ratio: parseFloat((currentVolume / currentVolSMA).toFixed(2)),
+            atr: parseFloat(currentATR.toFixed(2))
+        };
+
+        // =========================================================
+        // 4. LOGIQUE DE DÉCISION (Cassure de Volatilité)
+        // =========================================================
+
+        // --- A. CONDITION DE VENTE (SELL) ---
+        // On est greedy, mais pas fous. Si l'élan s'effondre (Prix < EMA 9) ou RSI extrême (> 85), on encaisse !
+        if (currentPrice < currentEmaShort || currentRSI > 85) {
+            return {
+                action: ActionTypes.SELL || 'SELL',
+                reason: `Perte de Momentum (Prix < EMA 9) ou Surchauffe extrême (RSI: ${currentRSI.toFixed(2)}).`,
+                confidence: 0.8500,
+                indicators: currentIndicators,
+                stop_loss_price: null,
+                optimal_price: null
+            };
+        }
+
+        // --- B. CONDITION D'ACHAT (BUY) ---
+        const isTrendUp = currentEmaShort > currentEmaLong; // Tendance courte OK
+        const isBreakout = currentPrice >= currentBB.upper; // Cassure de la bande de Bollinger
+        const isHighVolume = currentVolume > (currentVolSMA * 1.5); // Volume au moins 50% supérieur à la moyenne
+
+        if (isTrendUp && isBreakout && isHighVolume) {
+            // Calcul de la confiance
+            let confidence = 0.60; // Base solide car on a la tendance + le breakout
+
+            // Si le volume est complètement fou (ex: 3x la normale), c'est un signal très fort !
+            if (currentVolume > currentVolSMA * 2.5) confidence += 0.25;
+            else if (currentVolume > currentVolSMA * 2.0) confidence += 0.15;
+
+            // Si le RSI n'est pas encore trop haut (il reste de la marge de montée)
+            if (currentRSI < 70) confidence += 0.10;
+
+            confidence = Math.min(confidence, 0.9900);
+
+            // Risk Management GREEDY (Ratio 1:3)
+            // Stop loss très serré, Take Profit très haut
+            const stopLossPrice = currentPrice - (1.2 * currentATR);
+            const takeProfitPrice = currentPrice + (3.6 * currentATR);
 
-        // Logique GREEDY : L'EMA courte passe au-dessus de l'EMA longue (Croisement haussier)
-        if (lastShort > lastLong) {
             return {
-                action: ActionTypes.BUY_ALERT,
-                reason: `Croisement Haussier Agressif (EMA ${shortPeriod} > EMA ${longPeriod})`,
-                confidence: 0.9,
-                indicators: { ema_short: lastShort, ema_long: lastLong }
+                action: ActionTypes.BUY || 'BUY',
+                reason: `Breakout Volatilité (Bollinger) avec fort Volume (${currentIndicators.vol_ratio}x).`,
+                confidence: parseFloat(confidence.toFixed(4)),
+                indicators: currentIndicators,
+                stop_loss_price: parseFloat(stopLossPrice.toFixed(4)),
+                optimal_price: parseFloat(takeProfitPrice.toFixed(4))
             };
         }
 
+        // --- C. CONDITION DE MAINTIEN (HOLD) ---
         return null;
     }
 }
diff --git a/Wallette/server/modules/strategy/strategies/SafeStrategy.js b/Wallette/server/modules/strategy/strategies/SafeStrategy.js
new file mode 100644 (file)
index 0000000..c4010af
--- /dev/null
@@ -0,0 +1,117 @@
+import TradingStrategy from './TradingStrategy.js';
+import { ActionTypes } from '../config/enum.js';
+import { EMA, RSI, MACD, ATR } from 'technicalindicators';
+
+class SafeStrategy extends TradingStrategy {
+    constructor() {
+        super('SAFE');
+    }
+
+    analyze(candles, userParams) {
+        // 1. Extraction des données
+        const closes = candles.map(c => parseFloat(c.close_price));
+        const highs = candles.map(c => parseFloat(c.high_price));
+        const lows = candles.map(c => parseFloat(c.low_price));
+
+        // 2. Paramètres ultra-conservateurs
+        const emaLongPeriod = userParams?.ema_long || 200; // Tendance macro obligatoire
+        const rsiPeriod = userParams?.rsi_period || 14;
+        const atrPeriod = userParams?.atr_period || 14;
+
+        // Paramètres standards du MACD (12, 26, 9)
+        const macdFast = 12;
+        const macdSlow = 26;
+        const macdSignal = 9;
+
+        // Sécurité : Le MACD et l'EMA 200 ont besoin de beaucoup d'historique
+        if (closes.length < emaLongPeriod) {
+            console.log(`[SAFE] Historique insuffisant. Requis: ${emaLongPeriod}, Reçu: ${closes.length}`);
+            return null;
+        }
+
+        // 3. Calculs des indicateurs
+        const emaLongVals = EMA.calculate({ period: emaLongPeriod, values: closes });
+        const rsiVals = RSI.calculate({ period: rsiPeriod, values: closes });
+        const atrVals = ATR.calculate({ high: highs, low: lows, close: closes, period: atrPeriod });
+
+        const macdVals = MACD.calculate({
+            values: closes,
+            fastPeriod: macdFast,
+            slowPeriod: macdSlow,
+            signalPeriod: macdSignal,
+            SimpleMAOscillator: false,
+            SimpleMASignal: false
+        });
+
+        // Récupération des valeurs (Actuelle et Précédente pour voir les croisements)
+        const currentPrice = closes[closes.length - 1];
+        const currentEmaLong = emaLongVals[emaLongVals.length - 1];
+        const currentRSI = rsiVals[rsiVals.length - 1];
+        const currentATR = atrVals[atrVals.length - 1];
+
+        const currentMACD = macdVals[macdVals.length - 1];
+        const prevMACD = macdVals[macdVals.length - 2];
+
+        // Indicateurs JSON pour la DB
+        const currentIndicators = {
+            ema_200: parseFloat(currentEmaLong.toFixed(2)),
+            rsi: parseFloat(currentRSI.toFixed(2)),
+            macd_hist: parseFloat(currentMACD.histogram?.toFixed(4) || 0),
+            atr: parseFloat(currentATR.toFixed(2))
+        };
+
+        // =========================================================
+        // 4. LOGIQUE DE DÉCISION (Protection du Capital)
+        // =========================================================
+
+        // --- A. CONDITION DE VENTE (SELL) ---
+        // En mode SAFE, on ne prend aucun risque. Dès que le RSI dépasse 60 (même pas 70 !)
+        // ou que le MACD recroise à la baisse, on sécurise l'argent.
+        if (currentRSI > 60 || (currentMACD.histogram < 0 && prevMACD.histogram > 0)) {
+            return {
+                action: ActionTypes.SELL || 'SELL',
+                reason: `Prise de profit sécurisée (RSI: ${currentRSI.toFixed(2)}) ou perte de momentum MACD.`,
+                confidence: 0.9000, // Très confiant qu'il faut sortir
+                indicators: currentIndicators,
+                stop_loss_price: null,
+                optimal_price: null
+            };
+        }
+
+        // --- B. CONDITION D'ACHAT (BUY) ---
+        // 1. Le prix est au-dessus de l'EMA 200 (On est en Bull Market)
+        const isMacroUptrend = currentPrice > currentEmaLong;
+        // 2. Le prix a subi un crash très récent (RSI bas)
+        const isDeepOversold = currentRSI < 30;
+        // 3. Le couteau a fini de tomber : l'histogramme MACD repasse dans le vert
+        const isBouncing = currentMACD.histogram > 0 && prevMACD.histogram <= 0;
+
+        if (isMacroUptrend && isDeepOversold && isBouncing) {
+
+            // Calcul de la confiance (démarre très haut car les conditions sont dures à réunir)
+            let confidence = 0.80;
+
+            if (currentRSI < 20) confidence += 0.15; // Crash exceptionnel = opportunité en or
+
+            confidence = Math.min(confidence, 0.9900);
+
+            // Risk Management SAFE (Ratio 1:1, Stop Loss large pour éviter le bruit)
+            const stopLossPrice = currentPrice - (2.0 * currentATR);
+            const takeProfitPrice = currentPrice + (2.0 * currentATR);
+
+            return {
+                action: ActionTypes.BUY || 'BUY',
+                reason: `Rebond MACD validé en zone de survente (RSI: ${currentRSI.toFixed(2)}) sur tendance haussière.`,
+                confidence: parseFloat(confidence.toFixed(4)),
+                indicators: currentIndicators,
+                stop_loss_price: parseFloat(stopLossPrice.toFixed(4)),
+                optimal_price: parseFloat(takeProfitPrice.toFixed(4))
+            };
+        }
+
+        // --- C. CONDITION DE MAINTIEN (HOLD) ---
+        return null;
+    }
+}
+
+export default SafeStrategy;
\ No newline at end of file
index 17a0c6f7375b83d65fb3dd6aab41d7fe1db4a4a7..f690a9cae965b15f47f016b88ab04da6d684b04b 100644 (file)
@@ -1,6 +1,6 @@
 import TradingStrategy from './TradingStrategy.js';
-import { ActionTypes } from '../config/enum.js'
-import { RSI, SMA } from 'technicalindicators';
+import { ActionTypes } from '../config/enum.js';
+import { RSI, EMA, ATR, SMA } from 'technicalindicators';
 
 class StandardStrategy extends TradingStrategy {
     constructor() {
@@ -8,45 +8,96 @@ class StandardStrategy extends TradingStrategy {
     }
 
     analyze(candles, userParams) {
-        // Extrait le prix de fermeture
-        const prices = candles.map(c => parseFloat(c.close_price));
+        // 1. Extraction des données nécessaires pour tous les indicateurs
+        const closes = candles.map(c => parseFloat(c.close_price));
+        const highs = candles.map(c => parseFloat(c.high_price));
+        const lows = candles.map(c => parseFloat(c.low_price));
+        const volumes = candles.map(c => parseFloat(c.volume || 0));
 
-        // Récupération des params (ou valeurs par défaut pour le test)
-        // En prod, ce sera RSI=14 et SMA=50
+        // 2. Paramètres de la stratégie (Valeurs fiables pour le Trend-Following)
         const rsiPeriod = userParams?.rsi_period || 14;
-        const smaPeriod = userParams?.sma_period || 50;
+        const emaShortPeriod = userParams?.ema_short || 50;
+        const emaLongPeriod = userParams?.ema_long || 200; // Tendance de fond
+        const atrPeriod = userParams?.atr_period || 14;    // Volatilité pour le Stop Loss
 
-        // Vérification de sécurité : A-t-on assez de données ?
-        if (prices.length < smaPeriod) {
-            console.log(`Pas assez de données pour ${this.name} (Requis: ${smaPeriod}, Reçu: ${prices.length})`);
-            return null;
+        // SÉCURITÉ : A-t-on assez de données pour l'EMA 200 ?
+        if (closes.length < emaLongPeriod) {
+            console.log(`[STANDARD] Pas assez de données. Requis: ${emaLongPeriod}, Reçu: ${closes.length}`);
+            return null; // On annule l'analyse sans crasher
         }
 
-        // Calcul mathémathique
-        const rsiValues = RSI.calculate({ period: rsiPeriod, values: prices });
-        const smaValues = SMA.calculate({ period: smaPeriod, values: prices });
+        // 3. Calculs des indicateurs (librairie technicalindicators)
+        const rsiValues = RSI.calculate({ period: rsiPeriod, values: closes });
+        const emaShortValues = EMA.calculate({ period: emaShortPeriod, values: closes });
+        const emaLongValues = EMA.calculate({ period: emaLongPeriod, values: closes });
+        const atrValues = ATR.calculate({ high: highs, low: lows, close: closes, period: atrPeriod });
+        const volSmaValues = SMA.calculate({ period: 20, values: volumes });
 
-        // On récup les dernières valeurs calculées
-        const lastRSI = rsiValues[rsiValues.length - 1];
-        const lastSMA = smaValues[smaValues.length - 1];
-        const currentPrice = prices[prices.length - 1];
+        // Récupération des valeurs actuelles (la dernière bougie)
+        const currentPrice = closes[closes.length - 1];
+        const currentRSI = rsiValues[rsiValues.length - 1];
+        const currentEmaShort = emaShortValues[emaShortValues.length - 1];
+        const currentEmaLong = emaLongValues[emaLongValues.length - 1];
+        const prevEmaLong = emaLongValues[emaLongValues.length - 2]; // Pour analyser la pente
+        const currentATR = atrValues[atrValues.length - 1];
+        const currentVolume = volumes[volumes.length - 1];
+        const currentVolSMA = volSmaValues[volSmaValues.length - 1];
 
-        console.log(`Analyse ${this.name} -> Prix: ${currentPrice} | SMA(${smaPeriod}): ${lastSMA} | RSI(${rsiPeriod}): ${lastRSI}`);
+        // Objet JSON qui sera stocké dans signals.indicators
+        const currentIndicators = {
+            rsi: parseFloat(currentRSI.toFixed(2)),
+            ema_short: parseFloat(currentEmaShort.toFixed(2)),
+            ema_long: parseFloat(currentEmaLong.toFixed(2)),
+            atr: parseFloat(currentATR.toFixed(2))
+        };
 
-        // 3. Logique de décision
-        // Achat si : Prix au-dessus de la SMA (Tendance Haussière) ET RSI bas (Pas cher)
-        // Seuil RSI standard = 35 (ici j'ai mis 100 pour forcer le signal lors du test !)
-        const rsiThreshold = 35;
+        // =========================================================
+        // 4. LOGIQUE DE DÉCISION (Variante A : Trend-Following)
+        // =========================================================
 
-        if (currentPrice > lastSMA && lastRSI < rsiThreshold) {
+        // --- A. CONDITION DE VENTE (SELL) ---
+        // On vend si le marché est en surchauffe ou si la tendance casse à la baisse
+        if (currentRSI > 75 || currentPrice < currentEmaShort) {
             return {
-                action: ActionTypes.BUY_ALERT,
-                reason: `Tendance Haussière (Prix > SMA) & RSI Favorable (${lastRSI.toFixed(2)})`,
-                confidence: 0.8,
-                indicators: { rsi: lastRSI, sma: lastSMA }
+                action: ActionTypes.SELL || 'SELL',
+                reason: `Surchauffe (RSI: ${currentRSI.toFixed(2)}) ou cassure de l'EMA 50.`,
+                confidence: 0.8500,
+                indicators: currentIndicators,
+                stop_loss_price: null,
+                optimal_price: null
             };
         }
 
+        // --- B. CONDITION D'ACHAT (BUY) ---
+        const isUptrend = currentEmaShort > currentEmaLong; // Tendance de fond haussière
+        const isPullback = currentRSI < 45 && currentPrice > currentEmaShort; // Repli (pas cher)
+
+        if (isUptrend && isPullback) {
+            // Calcul de la confiance déterministe (Confluence)
+            let confidence = 0.50;
+            if (currentEmaLong > prevEmaLong) confidence += 0.15; // L'EMA 200 monte
+            if (currentRSI < 35) confidence += 0.15; // Le repli est profond (opportunité)
+            if (currentVolume > currentVolSMA) confidence += 0.10; // Appui des acheteurs (volume)
+
+            // Sécurité : Max 0.99
+            confidence = Math.min(confidence, 0.9900);
+
+            // Risk Management (ATR)
+            const stopLossPrice = currentPrice - (1.5 * currentATR);
+            const takeProfitPrice = currentPrice + (3.0 * currentATR); // Ratio R/R de 1:2
+
+            return {
+                action: ActionTypes.BUY || 'BUY',
+                reason: `Tendance Haussière confirmée & Pullback (RSI: ${currentRSI.toFixed(2)})`,
+                confidence: parseFloat(confidence.toFixed(4)),
+                indicators: currentIndicators,
+                stop_loss_price: parseFloat(stopLossPrice.toFixed(4)),
+                optimal_price: parseFloat(takeProfitPrice.toFixed(4))
+            };
+        }
+
+        // --- C. CONDITION DE MAINTIEN (HOLD) ---
+        // Aucun signal particulier, on renvoie null pour ne pas spammer la base de données
         return null;
     }
 }