From: Sacheat Date: Sun, 1 Mar 2026 21:11:42 +0000 (+0100) Subject: Implémentation amélioration des stratégies et ajout de la stratégie SAFE X-Git-Url: https://git.digitality.be/?a=commitdiff_plain;h=8f5f47b16db198f016968d22c62a8ed10e3705fb;p=pdw25-26 Implémentation amélioration des stratégies et ajout de la stratégie SAFE --- diff --git a/Wallette/server/modules/strategy/factories/StrategyFactory.js b/Wallette/server/modules/strategy/factories/StrategyFactory.js index 21c8b67..efbf5ed 100644 --- a/Wallette/server/modules/strategy/factories/StrategyFactory.js +++ b/Wallette/server/modules/strategy/factories/StrategyFactory.js @@ -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(); diff --git a/Wallette/server/modules/strategy/services/BotService.js b/Wallette/server/modules/strategy/services/BotService.js index d1b1f2e..d1fa9b9 100644 --- a/Wallette/server/modules/strategy/services/BotService.js +++ b/Wallette/server/modules/strategy/services/BotService.js @@ -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}) } ); diff --git a/Wallette/server/modules/strategy/strategies/GreedyStrategy.js b/Wallette/server/modules/strategy/strategies/GreedyStrategy.js index 7aeabc6..d0d75ab 100644 --- a/Wallette/server/modules/strategy/strategies/GreedyStrategy.js +++ b/Wallette/server/modules/strategy/strategies/GreedyStrategy.js @@ -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 index 0000000..c4010af --- /dev/null +++ b/Wallette/server/modules/strategy/strategies/SafeStrategy.js @@ -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 diff --git a/Wallette/server/modules/strategy/strategies/StandardStrategy.js b/Wallette/server/modules/strategy/strategies/StandardStrategy.js index 17a0c6f..f690a9c 100644 --- a/Wallette/server/modules/strategy/strategies/StandardStrategy.js +++ b/Wallette/server/modules/strategy/strategies/StandardStrategy.js @@ -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; } }