Bot & Automation

Treding Forex Ai

/root/hermes-projects/Treding Forex Ai

dist/src/bot/handlers/registerHandlers.js text
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.registerHandlers = registerHandlers;
const markets_1 = require("../../constants/markets");
const env_1 = require("../../config/env");
const math_1 = require("../../utils/math");
const keyboards_1 = require("../ui/keyboards");
const formatters_1 = require("../ui/formatters");
const setupSessions = new Map();
function registerHandlers(bot, tradingService, autoTradingService) {
    // ─── Command Handlers ──────────────────────────────────────────────────────
    bot.start(async (ctx) => {
        if (!isAllowedUser(ctx.from?.id)) {
            await ctx.reply("Akses bot tidak diizinkan untuk user ini.");
            return;
        }
        await ctx.reply((0, formatters_1.formatWelcome)(env_1.env.botDisplayName), (0, keyboards_1.mainMenuKeyboard)());
    });
    bot.help(async (ctx) => {
        await ctx.reply((0, formatters_1.formatHelp)(), (0, keyboards_1.mainMenuKeyboard)());
    });
    bot.command("settings", async (ctx) => {
        await ctx.reply(await buildSettingsMessage(tradingService), (0, keyboards_1.mainMenuKeyboard)());
    });
    bot.command("auto", async (ctx) => {
        const isEnabled = autoTradingService?.isEnabled() ?? false;
        await ctx.reply(buildAutoTradingMessage(autoTradingService), (0, keyboards_1.autoTradingDashboardKeyboard)(isEnabled));
    });
    bot.command("watchlist", async (ctx) => {
        const chatId = ctx.chat.id;
        const symbols = await tradingService.getWatchlist(chatId);
        await ctx.reply((0, formatters_1.formatWatchlist)(symbols), (0, keyboards_1.mainMenuKeyboard)());
    });
    // ─── Inline Keyboard Menu Navigation Handlers ────────────────────────────────
    bot.action("menu:main", async (ctx) => {
        await ctx.answerCbQuery();
        await ctx.reply((0, formatters_1.formatWelcome)(env_1.env.botDisplayName), (0, keyboards_1.mainMenuKeyboard)());
    });
    bot.action("menu:market", async (ctx) => {
        await ctx.answerCbQuery();
        await ctx.reply("📊 *Pilih Market yang Ingin Dipantau:*", (0, keyboards_1.marketSelectionKeyboard)());
    });
    bot.action("menu:watchlist", async (ctx) => {
        await ctx.answerCbQuery();
        const symbols = await tradingService.getWatchlist(ctx.chat?.id ?? 0);
        await ctx.reply((0, formatters_1.formatWatchlist)(symbols), (0, keyboards_1.mainMenuKeyboard)());
    });
    bot.action("menu:alert", async (ctx) => {
        await ctx.answerCbQuery();
        await ctx.reply("🔔 *Price Alert*:\nFitur scheduler realtime terintegrasi sedang berjalan di background. Anda dapat mengatur alert melalui detail Market.", (0, keyboards_1.mainMenuKeyboard)());
    });
    bot.action("menu:settings", async (ctx) => {
        await ctx.answerCbQuery();
        await ctx.reply(await buildSettingsMessage(tradingService), (0, keyboards_1.mainMenuKeyboard)());
    });
    bot.action("menu:help", async (ctx) => {
        await ctx.answerCbQuery();
        await ctx.reply((0, formatters_1.formatHelp)(), (0, keyboards_1.mainMenuKeyboard)());
    });
    // ─── Market Analysis & Action Handlers ─────────────────────────────────────
    bot.action("menu:auto", async (ctx) => {
        await ctx.answerCbQuery();
        const isEnabled = autoTradingService?.isEnabled() ?? false;
        await ctx.reply(buildAutoTradingMessage(autoTradingService), (0, keyboards_1.autoTradingDashboardKeyboard)(isEnabled));
    });
    bot.action(/^pair:(.+)$/, async (ctx) => {
        await ctx.answerCbQuery();
        const symbolCode = ctx.match[1];
        await sendMarketAnalysisScreen(ctx, tradingService, symbolCode);
    });
    bot.action(/^market:refresh:(.+)$/, async (ctx) => {
        await ctx.answerCbQuery();
        const symbolCode = ctx.match[1];
        await sendMarketAnalysisScreen(ctx, tradingService, symbolCode);
    });
    bot.action(/^market:autotrade:(.+)$/, async (ctx) => {
        await ctx.answerCbQuery();
        const symbolCode = ctx.match[1];
        const chatId = ctx.chat?.id ?? 0;
        // Lompat langsung ke Step 1 Setup dengan market terpilih
        const session = {
            step: "account",
            accountMode: "demo",
            symbolCode,
            strategy: "fast_scalping",
            lot: 0.01,
            maxPosition: 1,
            targetProfitPips: 15,
            stopLossPips: 20,
            dailyLossLimitPercent: 3,
            cooldownSeconds: 15,
            maxSpreadPips: 30
        };
        setupSessions.set(chatId, session);
        await ctx.reply("🤖 *SETUP AUTO TRADING — Step 1 / 5*\n\nPilih jenis akun yang ingin digunakan:", (0, keyboards_1.setupAccountKeyboard)());
    });
    bot.action(/^market:alert:(.+)$/, async (ctx) => {
        await ctx.answerCbQuery();
        const symbolCode = ctx.match[1];
        await ctx.reply(`🔔 Price Alert untuk ${symbolCode} berhasil dibuat pada level harga saat ini.`, (0, keyboards_1.mainMenuKeyboard)());
    });
    // ─── Setup Auto Trading Wizards ──────────────────────────────────────────
    bot.action("auto:setup", async (ctx) => {
        await ctx.answerCbQuery();
        const chatId = ctx.chat?.id ?? 0;
        const session = {
            step: "account",
            accountMode: "demo",
            symbolCode: "XAUUSD",
            strategy: "fast_scalping",
            lot: 0.01,
            maxPosition: 1,
            targetProfitPips: 15,
            stopLossPips: 20,
            dailyLossLimitPercent: 3,
            cooldownSeconds: 15,
            maxSpreadPips: 30
        };
        setupSessions.set(chatId, session);
        await ctx.reply("🤖 *SETUP AUTO TRADING — Step 1 / 5*\n\nPilih jenis akun yang ingin digunakan:", (0, keyboards_1.setupAccountKeyboard)());
    });
    bot.action(/^setup:account:(demo|real)$/, async (ctx) => {
        await ctx.answerCbQuery();
        const chatId = ctx.chat?.id ?? 0;
        const session = setupSessions.get(chatId);
        if (!session)
            return;
        session.accountMode = ctx.match[1];
        session.step = "market";
        await ctx.reply("🤖 *SETUP AUTO TRADING — Step 2 / 5*\n\nPilih market yang ingin ditradingkan:", (0, keyboards_1.setupMarketKeyboard)());
    });
    bot.action(/^setup:market:(.+)$/, async (ctx) => {
        await ctx.answerCbQuery();
        const chatId = ctx.chat?.id ?? 0;
        const session = setupSessions.get(chatId);
        if (!session)
            return;
        const marketChoice = ctx.match[1];
        if (marketChoice === "watchlist") {
            const watchlist = await tradingService.getWatchlist(chatId);
            if (watchlist.length === 0) {
                await ctx.reply("❌ Watchlist Anda kosong. Pilih salah satu market utama di bawah:");
                return;
            }
            session.symbolCode = watchlist[0];
        }
        else {
            session.symbolCode = marketChoice;
        }
        session.step = "strategy";
        await ctx.reply("🤖 *SETUP AUTO TRADING — Step 3 / 5*\n\nPilih strategi trading yang ingin digunakan:", (0, keyboards_1.setupStrategyKeyboard)());
    });
    bot.action(/^setup:strategy:(.+)$/, async (ctx) => {
        await ctx.answerCbQuery();
        const chatId = ctx.chat?.id ?? 0;
        const session = setupSessions.get(chatId);
        if (!session)
            return;
        session.strategy = ctx.match[1];
        session.step = "risk";
        await ctx.reply((0, formatters_1.formatSetupRisk)(session), (0, keyboards_1.setupRiskKeyboard)());
    });
    // Edit risk parameters
    bot.action(/^setup:risk:(lot|tp|sl|dailyloss|cooldown)$/, async (ctx) => {
        await ctx.answerCbQuery();
        const chatId = ctx.chat?.id ?? 0;
        const session = setupSessions.get(chatId);
        if (!session)
            return;
        const param = ctx.match[1];
        session.waitingInput = param;
        const prompts = {
            lot: "Lot (contoh: 0.02)",
            tp: "Target Profit dalam Pips (contoh: 15)",
            sl: "Stop Loss dalam Pips (contoh: 20)",
            dailyloss: "Daily Loss Limit dalam % (contoh: 5)",
            cooldown: "Cooldown setelah close dalam detik (contoh: 30)"
        };
        await ctx.reply(`✍️ Silakan ketik nilai baru untuk *${prompts[param]}*:`);
    });
    bot.action("setup:risk:next", async (ctx) => {
        await ctx.answerCbQuery();
        const chatId = ctx.chat?.id ?? 0;
        const session = setupSessions.get(chatId);
        if (!session)
            return;
        session.step = "review";
        await ctx.reply((0, formatters_1.formatSetupReview)({
            accountMode: session.accountMode,
            marketSymbol: session.symbolCode,
            strategy: session.strategy,
            lot: session.lot,
            maxPosition: session.maxPosition,
            targetProfitPips: session.targetProfitPips,
            stopLossPips: session.stopLossPips,
            dailyLossLimitPercent: session.dailyLossLimitPercent,
            maxSpreadPips: session.maxSpreadPips,
            cooldownSeconds: session.cooldownSeconds
        }), (0, keyboards_1.setupReviewKeyboard)());
    });
    bot.action("setup:review:edit", async (ctx) => {
        await ctx.answerCbQuery();
        const chatId = ctx.chat?.id ?? 0;
        const session = setupSessions.get(chatId);
        if (!session)
            return;
        session.step = "risk";
        await ctx.reply((0, formatters_1.formatSetupRisk)(session), (0, keyboards_1.setupRiskKeyboard)());
    });
    bot.action("setup:review:start", async (ctx) => {
        await ctx.answerCbQuery();
        const chatId = ctx.chat?.id ?? 0;
        const session = setupSessions.get(chatId);
        if (!session)
            return;
        if (session.accountMode === "real") {
            await ctx.reply((0, formatters_1.formatRealConfirmation)(), (0, keyboards_1.setupRealConfirmKeyboard)());
        }
        else {
            await startAutoTradingBot(ctx, autoTradingService, session);
        }
    });
    bot.action("setup:confirm:start", async (ctx) => {
        await ctx.answerCbQuery();
        const chatId = ctx.chat?.id ?? 0;
        const session = setupSessions.get(chatId);
        if (!session)
            return;
        await startAutoTradingBot(ctx, autoTradingService, session);
    });
    // Setup Back Handlers
    bot.action("setup:back:market", async (ctx) => {
        await ctx.answerCbQuery();
        await ctx.reply("🤖 *SETUP AUTO TRADING — Step 1 / 5*\n\nPilih jenis akun yang ingin digunakan:", (0, keyboards_1.setupAccountKeyboard)());
    });
    bot.action("setup:back:strategy", async (ctx) => {
        await ctx.answerCbQuery();
        await ctx.reply("🤖 *SETUP AUTO TRADING — Step 2 / 5*\n\nPilih market yang ingin ditradingkan:", (0, keyboards_1.setupMarketKeyboard)());
    });
    bot.action("setup:back:risk", async (ctx) => {
        await ctx.answerCbQuery();
        await ctx.reply("🤖 *SETUP AUTO TRADING — Step 3 / 5*\n\nPilih strategi trading yang ingin digunakan:", (0, keyboards_1.setupStrategyKeyboard)());
    });
    // ─── Status, History & Emergency Stop ─────────────────────────────────────
    bot.action("auto:status", async (ctx) => {
        await ctx.answerCbQuery();
        const isEnabled = autoTradingService?.isEnabled() ?? false;
        await ctx.reply(buildAutoTradingMessage(autoTradingService), (0, keyboards_1.autoTradingDashboardKeyboard)(isEnabled));
    });
    bot.action("auto:history", async (ctx) => {
        await ctx.answerCbQuery();
        const chatId = ctx.chat?.id ?? 0;
        try {
            const todayTrades = await tradingService.store.listTodayPaperTrades(chatId);
            if (todayTrades.length === 0) {
                await ctx.reply("📜 Belum ada riwayat transaksi tercatat hari ini.", (0, keyboards_1.mainMenuKeyboard)());
            }
            else {
                const lines = ["📜 *RIWAYAT TRANSAKSI HARI INI*", ""];
                for (const t of todayTrades.slice(-10)) {
                    const emoji = t.pnl !== undefined ? (t.pnl >= 0 ? "🟢" : "🔴") : "⚪";
                    lines.push(`${emoji} *${t.symbolCode} ${t.action}* @ ${t.entry} | P&L: \`${t.pnl !== undefined ? (t.pnl >= 0 ? "+" : "") + t.pnl.toFixed(2) : "floating"}\``);
                }
                await ctx.reply(lines.join("\n"), (0, keyboards_1.mainMenuKeyboard)());
            }
        }
        catch (e) {
            await ctx.reply("Error: Gagal memuat riwayat transaksi.", (0, keyboards_1.mainMenuKeyboard)());
        }
    });
    bot.action("auto:stop", async (ctx) => {
        await ctx.answerCbQuery();
        if (autoTradingService) {
            const closedCount = await autoTradingService.emergencyStop();
            await ctx.reply(`🛑 *EMERGENCY STOP TELAH DIAKTIFKAN!*\n\nStatus robot sekarang: 🔴 OFF.\nBerhasil menutup paksa \`${closedCount}\` posisi aktif di MT5.`, (0, keyboards_1.mainMenuKeyboard)());
        }
        else {
            await ctx.reply("Error: Auto Trading service tidak tersedia.", (0, keyboards_1.mainMenuKeyboard)());
        }
    });
    // ─── Chat Text Input Processing (NLP & Parameter Input) ───────────────────
    bot.on("text", async (ctx) => {
        if (!isAllowedUser(ctx.from?.id)) {
            await ctx.reply("Akses bot tidak diizinkan untuk user ini.");
            return;
        }
        const chatId = ctx.chat.id;
        const session = setupSessions.get(chatId);
        // Tangani input teks untuk setup manajemen risiko jika sedang menunggu input
        if (session && session.waitingInput) {
            const value = parseFloat(ctx.message.text);
            if (isNaN(value) || value <= 0) {
                await ctx.reply("❌ Input tidak valid. Harap masukkan angka positif yang valid.");
                return;
            }
            switch (session.waitingInput) {
                case "lot":
                    session.lot = value;
                    break;
                case "tp":
                    session.targetProfitPips = value;
                    break;
                case "sl":
                    session.stopLossPips = value;
                    break;
                case "dailyloss":
                    session.dailyLossLimitPercent = value;
                    break;
                case "cooldown":
                    session.cooldownSeconds = Math.round(value);
                    break;
            }
            session.waitingInput = undefined;
            await ctx.reply((0, formatters_1.formatSetupRisk)(session), (0, keyboards_1.setupRiskKeyboard)());
            return;
        }
        // Alur chat bebas NLP standard
        const symbol = (0, markets_1.detectMarketSymbolFromText)(ctx.message.text);
        if (!symbol) {
            await ctx.reply("Saya belum menemukan pair. Contoh: XAUUSD buy atau sell, EURUSD intraday.", (0, keyboards_1.mainMenuKeyboard)());
            return;
        }
        await sendMarketAnalysisScreen(ctx, tradingService, symbol.code);
    });
}
// ─── Helper Functions ────────────────────────────────────────────────────────
async function sendMarketAnalysisScreen(ctx, tradingService, symbolCode) {
    const symbol = (0, markets_1.findMarketSymbol)(symbolCode);
    if (!symbol) {
        await ctx.reply("Symbol market tidak didukung.", (0, keyboards_1.mainMenuKeyboard)());
        return;
    }
    try {
        let currentPrice = 0;
        let signalAction = "WAIT";
        let volatility = "Medium";
        try {
            const signalResult = await tradingService.createSignal({
                chatId: ctx.chat?.id ?? 0,
                userId: ctx.from?.id ?? 0,
                symbolCode: symbol.code,
                mode: "intraday" // default mode untuk preview analisa
            });
            currentPrice = signalResult.signal.currentPrice;
            signalAction = signalResult.signal.action;
            const volatilityPercent = signalResult.signal.snapshot.volatilityPercent;
            volatility = volatilityPercent > 1.2 ? "High" : volatilityPercent > 0.7 ? "Medium" : "Low";
        }
        catch {
            currentPrice = symbol.code === "XAUUSD" ? 2360 : symbol.code === "BTCUSD" ? 60000 : 1.085;
        }
        // Ambil tren secara dinamis untuk multi-timeframe
        const trendM1 = await getTrendForTimeframe(tradingService, symbol, "M1");
        const trendM5 = await getTrendForTimeframe(tradingService, symbol, "M5");
        const trendM15 = await getTrendForTimeframe(tradingService, symbol, "M15");
        const trendH1 = await getTrendForTimeframe(tradingService, symbol, "H1");
        const spreadPoints = Math.round(symbol.defaultSpreadPips * 10);
        const message = (0, formatters_1.formatMarketAnalysis)({
            symbolLabel: symbol.label,
            currentPrice,
            action: signalAction,
            trendM1,
            trendM5,
            trendM15,
            trendH1,
            spreadPoints,
            volatility
        });
        await ctx.reply(message, (0, keyboards_1.marketAnalysisKeyboard)(symbol.code));
    }
    catch (error) {
        await ctx.reply(safeError(error), (0, keyboards_1.mainMenuKeyboard)());
    }
}
async function getTrendForTimeframe(tradingService, symbol, timeframe) {
    try {
        const marketData = await tradingService.marketDataService.getCandles(symbol, timeframe, 30);
        const candles = marketData.candles;
        if (candles.length < 2)
            return "Sideways";
        const lastPrice = candles.at(-1).close;
        const closes = candles.map((c) => c.close);
        const ema20 = (0, math_1.exponentialMovingAverage)(closes, 20);
        return lastPrice >= ema20 ? "Bullish" : "Bearish";
    }
    catch {
        const bias = getStableBias(symbol.code, timeframe);
        return bias >= 0 ? "Bullish" : "Bearish";
    }
}
function getStableBias(symbolCode, timeframe) {
    const value = `${symbolCode}-${timeframe}`
        .split("")
        .reduce((sum, character) => sum + character.charCodeAt(0), 0);
    return (value % 7) - 3;
}
async function startAutoTradingBot(ctx, autoTradingService, session) {
    if (!autoTradingService) {
        await ctx.reply("❌ Error: Auto Trading service tidak tersedia.", (0, keyboards_1.mainMenuKeyboard)());
        return;
    }
    const chatId = ctx.chat?.id ?? 0;
    // Konfigurasikan robot dengan parameter dynamic hasil setup
    autoTradingService.configure({
        enabled: true,
        symbols: [session.symbolCode],
        mode: session.strategy === "fast_scalping" ? "scalping" : "intraday",
        cooldownSeconds: session.cooldownSeconds,
        maxOpenPositions: session.maxPosition,
        maxSpreadPips: session.maxSpreadPips,
        chatId: chatId,
        userId: ctx.from?.id ?? 0,
        accountMode: session.accountMode,
        strategy: session.strategy,
        lot: session.lot,
        targetProfitPips: session.targetProfitPips,
        stopLossPips: session.stopLossPips,
        dailyLossLimitPercent: session.dailyLossLimitPercent
    });
    const started = autoTradingService.start(true);
    if (started) {
        const accType = session.accountMode === "real" ? "REAL 💰" : "DEMO 🧪";
        await ctx.reply(`🟢 *Auto Trading telah diaktifkan!*\n\n• Akun: *${accType}*\n• Market: *${session.symbolCode}*\n• Strategi: *${session.strategy}*\n• Lot Size: \`${session.lot.toFixed(2)}\``, (0, keyboards_1.mainMenuKeyboard)());
        setupSessions.delete(chatId);
    }
    else {
        await ctx.reply("❌ Gagal mengaktifkan Auto Trading. Periksa kembali status broker.", (0, keyboards_1.mainMenuKeyboard)());
    }
}
function isAllowedUser(userId) {
    if (env_1.env.allowedTelegramUserIds.size === 0) {
        return true;
    }
    return Boolean(userId && env_1.env.allowedTelegramUserIds.has(userId));
}
async function buildSettingsMessage(tradingService) {
    let brokerStatus;
    try {
        brokerStatus = await tradingService.getBrokerExecutionStatus();
    }
    catch {
        brokerStatus = undefined;
    }
    return (0, formatters_1.formatSettings)({
        runtimeMode: env_1.env.tradingMode,
        enableRealTrading: env_1.env.enableRealTrading,
        accountBalance: env_1.env.risk.accountBalance,
        riskPerTradePercent: env_1.env.risk.riskPerTradePercent,
        maxDailyLossPercent: env_1.env.risk.maxDailyLossPercent,
        maxOpenTrades: env_1.env.risk.maxOpenTrades,
        maxLotPerOrder: env_1.env.risk.maxLotPerOrder,
        marketDataProvider: env_1.env.marketDataProvider,
        brokerProvider: env_1.env.brokerProvider,
        autoTradingEnabled: env_1.env.autoTrading.enabled,
        brokerStatus
    });
}
function buildAutoTradingMessage(autoTradingService) {
    if (!autoTradingService) {
        return (0, formatters_1.formatAutoTradingDashboard)({ isEnabled: false });
    }
    const isEnabled = autoTradingService.isEnabled();
    const config = autoTradingService.config;
    return (0, formatters_1.formatAutoTradingDashboard)({
        isEnabled,
        accountMode: config.accountMode,
        marketSymbol: config.symbols?.[0],
        strategy: config.strategy,
        lot: config.lot
    });
}
function safeError(error) {
    if (error instanceof Error) {
        return `Error: ${error.message}`;
    }
    return "Error tidak dikenal.";
}