Bot & Automation

Treding Forex Ai

/root/hermes-projects/Treding Forex Ai

dist/src/services/strategy/forexStrategy.js text
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ForexStrategy = void 0;
const node_crypto_1 = require("node:crypto");
const risk_1 = require("../../constants/risk");
const math_1 = require("../../utils/math");
class ForexStrategy {
    marketDataService;
    constructor(marketDataService) {
        this.marketDataService = marketDataService;
    }
    async analyze(symbol, mode) {
        const modeConfig = risk_1.TRADING_MODE_CONFIGS[mode];
        const primaryTimeframe = modeConfig.timeframes[1] ?? modeConfig.timeframes[0];
        const marketData = await this.marketDataService.getCandles(symbol, primaryTimeframe, 220);
        const candles = marketData.candles;
        if (candles.length < 60) {
            throw new Error("Data candle tidak cukup untuk analisis");
        }
        const closes = candles.map((candle) => candle.close);
        const currentPrice = closes.at(-1) ?? 0;
        const ema20 = (0, math_1.exponentialMovingAverage)(closes, 20);
        const ema50 = (0, math_1.exponentialMovingAverage)(closes, 50);
        const ema200 = (0, math_1.exponentialMovingAverage)(closes, 200);
        const rsi = (0, math_1.relativeStrengthIndex)(closes, 14);
        const atr = (0, math_1.averageTrueRange)(candles, 14);
        const macd = (0, math_1.macdHistogram)(closes);
        const levels = (0, math_1.supportResistance)(candles, 40);
        const volatilityPercent = currentPrice > 0 ? (atr / currentPrice) * 100 : 0;
        const snapshot = {
            ema20,
            ema50,
            ema200,
            rsi,
            atr,
            macdHistogram: macd,
            support: levels.support,
            resistance: levels.resistance,
            volatilityPercent
        };
        const bullishScore = scoreBullishSetup(snapshot, currentPrice);
        const bearishScore = scoreBearishSetup(snapshot, currentPrice);
        const directionScore = Math.max(bullishScore, bearishScore);
        let confidence = Math.round((0, math_1.clamp)(43 + directionScore * 8 - volatilityPenalty(volatilityPercent), 35, 88));
        const trend = describeTrend(snapshot);
        let action = confidence >= modeConfig.confidenceThreshold
            ? bullishScore > bearishScore
                ? "BUY"
                : "SELL"
            : "WAIT";
        // USER EXPLICIT OVERRIDE: For scalping, ALWAYS pick a direction (no WAIT)
        if (mode === "scalping" && action === "WAIT") {
            action = bullishScore >= bearishScore ? "BUY" : "SELL";
            confidence = Math.max(confidence, modeConfig.confidenceThreshold); // Force pass
        }
        let stopDistance = Math.max(atr * modeConfig.atrStopMultiplier, symbol.pipSize * symbol.defaultSpreadPips * 2);
        let tp1Distance = stopDistance * modeConfig.targetRiskReward;
        let tp2Distance = stopDistance * (modeConfig.targetRiskReward + 0.5);
        // USER EXPLICIT OVERRIDE: Extremely tight SL (1.20) and TP (0.80) for XAUUSD
        if (symbol.code === "XAUUSD") {
            stopDistance = 1.20;
            tp1Distance = 0.80;
            tp2Distance = 1.50;
        }
        else if (mode === "scalping") {
            const maxSlDistance = 25 * symbol.pipSize;
            const minSlDistance = 15 * symbol.pipSize;
            stopDistance = (0, math_1.clamp)(stopDistance, minSlDistance, maxSlDistance);
            const maxTp1Distance = 20 * symbol.pipSize;
            const minTp1Distance = 10 * symbol.pipSize;
            tp1Distance = (0, math_1.clamp)(tp1Distance, minTp1Distance, maxTp1Distance);
            tp2Distance = tp1Distance * 1.5;
        }
        const priceDigits = symbol.pipSize >= 0.01 ? 2 : 5;
        const entry = action === "WAIT" ? null : (0, math_1.roundTo)(currentPrice, priceDigits);
        const stopLoss = action === "BUY"
            ? (0, math_1.roundTo)(currentPrice - stopDistance, priceDigits)
            : action === "SELL"
                ? (0, math_1.roundTo)(currentPrice + stopDistance, priceDigits)
                : null;
        const takeProfit1 = action === "BUY"
            ? (0, math_1.roundTo)(currentPrice + tp1Distance, priceDigits)
            : action === "SELL"
                ? (0, math_1.roundTo)(currentPrice - tp1Distance, priceDigits)
                : null;
        const takeProfit2 = action === "BUY"
            ? (0, math_1.roundTo)(currentPrice + tp2Distance, priceDigits)
            : action === "SELL"
                ? (0, math_1.roundTo)(currentPrice - tp2Distance, priceDigits)
                : null;
        return {
            id: (0, node_crypto_1.randomUUID)(),
            symbol,
            mode,
            timeframe: primaryTimeframe,
            action,
            confidence,
            currentPrice: (0, math_1.roundTo)(currentPrice, priceDigits),
            trend,
            entry,
            stopLoss,
            takeProfit1,
            takeProfit2,
            riskLevel: classifyRisk(confidence, volatilityPercent),
            riskRewardRatio: action === "WAIT" ? 0 : modeConfig.targetRiskReward,
            reasons: buildReasons(snapshot, trend, bullishScore, bearishScore, action),
            invalidation: buildInvalidation(action, snapshot, priceDigits),
            snapshot,
            dataWarning: marketData.warning,
            createdAt: new Date().toISOString()
        };
    }
}
exports.ForexStrategy = ForexStrategy;
function scoreBullishSetup(snapshot, currentPrice) {
    let score = 0;
    if (snapshot.ema20 > snapshot.ema50) {
        score += 1;
    }
    if (snapshot.ema50 > snapshot.ema200) {
        score += 1;
    }
    if (currentPrice > snapshot.ema20) {
        score += 1;
    }
    if (snapshot.rsi > 52 && snapshot.rsi < 72) {
        score += 1;
    }
    if (snapshot.macdHistogram > 0) {
        score += 1;
    }
    return score;
}
function scoreBearishSetup(snapshot, currentPrice) {
    let score = 0;
    if (snapshot.ema20 < snapshot.ema50) {
        score += 1;
    }
    if (snapshot.ema50 < snapshot.ema200) {
        score += 1;
    }
    if (currentPrice < snapshot.ema20) {
        score += 1;
    }
    if (snapshot.rsi < 48 && snapshot.rsi > 28) {
        score += 1;
    }
    if (snapshot.macdHistogram < 0) {
        score += 1;
    }
    return score;
}
function describeTrend(snapshot) {
    if (snapshot.ema20 > snapshot.ema50 && snapshot.ema50 > snapshot.ema200) {
        return "Bullish terstruktur";
    }
    if (snapshot.ema20 < snapshot.ema50 && snapshot.ema50 < snapshot.ema200) {
        return "Bearish terstruktur";
    }
    return "Sideways / transisi";
}
function volatilityPenalty(volatilityPercent) {
    if (volatilityPercent > 1.2) {
        return 9;
    }
    if (volatilityPercent > 0.7) {
        return 5;
    }
    return 0;
}
function classifyRisk(confidence, volatilityPercent) {
    if (confidence >= 74 && volatilityPercent < 0.7) {
        return "Low";
    }
    if (confidence >= 60 && volatilityPercent < 1.2) {
        return "Medium";
    }
    return "High";
}
function buildReasons(snapshot, trend, bullishScore, bearishScore, action) {
    return [
        `Trend utama: ${trend}.`,
        `EMA score bullish ${bullishScore}/5 dan bearish ${bearishScore}/5.`,
        `RSI ${(0, math_1.roundTo)(snapshot.rsi, 2)} menunjukkan momentum ${describeRsi(snapshot.rsi)}.`,
        `MACD histogram ${(0, math_1.roundTo)(snapshot.macdHistogram, 5)}.`,
        `Support ${(0, math_1.roundTo)(snapshot.support, 5)} dan resistance ${(0, math_1.roundTo)(snapshot.resistance, 5)}.`,
        action === "WAIT"
            ? "Setup belum cukup bersih, prioritas WAIT."
            : "Setup memenuhi threshold mode, tetap wajib gunakan stop loss."
    ];
}
function describeRsi(rsi) {
    if (rsi >= 70) {
        return "overbought";
    }
    if (rsi <= 30) {
        return "oversold";
    }
    if (rsi > 52) {
        return "bullish ringan";
    }
    if (rsi < 48) {
        return "bearish ringan";
    }
    return "netral";
}
function buildInvalidation(action, snapshot, priceDigits) {
    if (action === "BUY") {
        return `Batal jika harga close di bawah EMA50 (${(0, math_1.roundTo)(snapshot.ema50, priceDigits)}) atau momentum MACD berubah negatif.`;
    }
    if (action === "SELL") {
        return `Batal jika harga close di atas EMA50 (${(0, math_1.roundTo)(snapshot.ema50, priceDigits)}) atau momentum MACD berubah positif.`;
    }
    return "Tunggu breakout valid atau struktur EMA lebih jelas sebelum entry.";
}