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.";
}