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();
async sendToAlertsModule(signalPayload) {
try {
-
+
const BROADCAST_URL = 'http://127.0.0.1:3003/test/broadcast-alert';
const response = await fetch(
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(signalPayload)
+ body: JSON.stringify({alert: signalPayload})
}
);
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() {
}
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;
}
}
--- /dev/null
+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
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() {
}
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;
}
}