2026-05-20 03:26:54 -07:00
import express from "express" ;
import path from "path" ;
import axios from "axios" ;
import { createServer as createViteServer } from "vite" ;
import * as OneSignal from 'onesignal-node' ;
2026-05-29 11:22:12 -07:00
import MetaApiPkg from "metaapi.cloud-sdk/dist/index" ;
2026-05-25 05:52:17 -07:00
const MetaApi = typeof MetaApiPkg === "function" ? MetaApiPkg : ( MetaApiPkg as any ). default || MetaApiPkg ;
2026-05-20 03:26:54 -07:00
2026-05-29 11:22:12 -07:00
2026-05-20 04:12:56 -07:00
// Import Firebase (Server-side compatible since v9 supports Node environments)
import { db } from "./src/lib/firebase" ;
2026-05-25 05:52:17 -07:00
import { doc , getDoc , setDoc , addDoc , collection , runTransaction , query , where , getDocs , updateDoc , orderBy , limit } from "firebase/firestore" ;
2026-05-20 04:12:56 -07:00
2026-06-10 14:58:02 -07:00
import derivRoutes from "./src/routes/deriv.routes.ts" ;
2026-05-20 03:26:54 -07:00
async function startServer() {
const app = express ();
const PORT = 3000 ;
app . use ( express . json ());
// API Routes
app . get ( "/api/health" , ( req , res ) => {
res . json ({ status : "ok" });
});
2026-06-10 14:58:02 -07:00
// Mount Deriv Routes
app . use ( "/api/deriv" , derivRoutes );
2026-05-25 05:52:17 -07:00
// --- BACKGROUND WORKER NODE (CRON JOB) ---
2026-05-29 11:22:12 -07:00
function getYfSymbol ( pair : string ) : string {
2026-05-31 12:31:13 -07:00
if ( pair . includes ( 'XAU' )) return 'GC=F' ;
2026-06-09 04:35:32 -07:00
if ( pair . includes ( 'XAG' )) return 'SI=F' ;
2026-05-31 12:31:13 -07:00
if ( pair . includes ( 'US100' )) return 'NQ=F' ;
if ( pair . includes ( 'US30' )) return 'YM=F' ;
2026-05-29 11:22:12 -07:00
if ( pair . includes ( 'BTC' )) return 'BTC-USD' ;
2026-06-09 04:35:32 -07:00
if ( pair . includes ( 'ETH' )) return 'ETH-USD' ;
2026-05-31 12:31:13 -07:00
// Clean punctuation and common broker suffixes
const cleanPair = pair . toString (). replace ( '/' , '' ). replace ( /\.M$/ , '' ). replace ( /\.std$/ , '' );
2026-05-29 11:22:12 -07:00
return cleanPair + '=X' ;
}
2026-06-09 04:35:32 -07:00
async function getCurrentPriceProxy ( symbol : string ) : Promise < number | null > {
const symStr = symbol . trim (). toUpperCase ();
if ( symStr . includes ( 'INDEX' ) || symStr . includes ( 'BOOM' ) || symStr . includes ( 'CRASH' ) || symStr . includes ( 'VIX' ) || symStr . includes ( 'VOLATILITY' ) || symStr . includes ( 'STEP' )) {
return null ; // Synthetic indices handled separately or simulated
}
2026-06-13 22:47:55 -07:00
const apiKey = process . env . TWELVE_DATA_API_KEY || "e3143522be4c4aac89e1f9a39925ed80" ;
if ( apiKey ) {
2026-06-09 04:35:32 -07:00
try {
let twelveSymbol = symStr ;
if ( twelveSymbol === 'GOLD' || twelveSymbol === 'OURO' ) twelveSymbol = 'XAU/USD' ;
if ( twelveSymbol === 'SILVER' || twelveSymbol === 'PRATA' ) twelveSymbol = 'XAG/USD' ;
2026-06-13 22:47:55 -07:00
const res = await axios . get ( `https://api.twelvedata.com/price?symbol= ${ twelveSymbol } &apikey= ${ apiKey } ` );
2026-06-09 04:35:32 -07:00
if ( res . data && res . data . price ) return parseFloat ( res . data . price );
} catch ( e : any ) {
// Fallback silently if TwelveData is rate-limited or fails
}
}
const isCrypto = symStr . includes ( 'BTC' ) || symStr . includes ( 'ETH' ) || symStr . includes ( 'USDT' ) || symStr . includes ( 'BNB' );
if ( isCrypto ) {
try {
const binanceSymbol = symStr . replace ( /[^A-Z0-9]/g , '' );
const res = await axios . get ( `https://api.binance.com/api/v3/ticker/price?symbol= ${ binanceSymbol } ` );
if ( res . data && res . data . price ) return parseFloat ( res . data . price );
} catch ( e ) {}
}
try {
let symbolYF = getYfSymbol ( symStr );
const yfRes = await axios . get ( `https://query1.finance.yahoo.com/v8/finance/chart/ ${ symbolYF } ?interval=1m&range=1d` , {
headers : { "User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" }
});
const result = yfRes . data . chart . result [ 0 ];
return parseFloat ( result . meta . regularMarketPrice );
} catch ( e : any ) {
console . warn ( "Yahoo Finance error:" , e . message );
}
return null ;
}
2026-05-29 11:22:12 -07:00
const sendTelegramAlertServer = async ( text : string , botToken : string , chatId : string , replyToMessageId? : number ) => {
2026-05-25 05:52:17 -07:00
try {
const payload : any = {
chat_id : chatId ,
text : text ,
parse_mode : 'Markdown'
};
if ( replyToMessageId ) {
payload . reply_to_message_id = replyToMessageId ;
}
const res = await axios . post ( `https://api.telegram.org/bot ${ botToken } /sendMessage` , payload );
return res . data ? . result ? . message_id ;
} catch ( error ) {
console . error ( 'Error sending Telegram alert from server:' , error );
return null ;
}
};
const checkAndMonitorActiveSignals = async ( botToken : string , chatId : string ) => {
try {
if ( ! botToken || ! chatId ) return ;
const signalsRef = collection ( db , "telegram_active_signals" );
const q = query ( signalsRef , where ( "status" , "==" , "ACTIVE" ));
const querySnapshot = await getDocs ( q );
for ( const docSnap of querySnapshot . docs ) {
const signal = docSnap . data ();
let currentPrice = null ;
try {
2026-06-09 04:35:32 -07:00
const symStr = signal . pair . toUpperCase ();
if ( symStr . includes ( 'INDEX' ) || symStr . includes ( 'BOOM' ) || symStr . includes ( 'CRASH' ) || symStr . includes ( 'VIX' ) || symStr . includes ( 'VOLATILITY' ) || symStr . includes ( 'STEP' )) {
2026-05-29 11:22:12 -07:00
const tp = parseFloat ( signal . takeProfit );
const sl = parseFloat ( signal . stopLoss );
// Simulamos um fecho aleatório para os índices para testes rápidos
if ( Math . random () > 0.8 ) {
currentPrice = Math . random () > 0.5 ? tp : sl ;
} else {
currentPrice = ( tp + sl ) / 2 ; // price stays in the middle
}
} else {
2026-06-09 04:35:32 -07:00
currentPrice = await getCurrentPriceProxy ( symStr );
2026-05-25 05:52:17 -07:00
}
2026-05-31 12:31:13 -07:00
} catch ( e : any ) {
2026-06-09 04:35:32 -07:00
console . error ( "Proxy price fetch error:" , e . message );
2026-05-25 05:52:17 -07:00
continue ;
}
if ( currentPrice === null ) continue ;
let closed = false ;
let resultText = "" ;
const sl = parseFloat ( signal . stopLoss );
const tp = parseFloat ( signal . takeProfit );
const entry = parseFloat ( signal . entry );
if ( signal . decision === 'BUY' ) {
if ( currentPrice <= sl ) {
closed = true ;
resultText = `❌ *Stop Loss Atingido!* \ nPreço final: ${ currentPrice } ` ;
} else if ( currentPrice >= tp ) {
closed = true ;
resultText = `✅ *Take Profit Atingido!* 🎯 \ nPreço final: ${ currentPrice } ` ;
}
} else if ( signal . decision === 'SELL' ) {
if ( currentPrice >= sl ) {
closed = true ;
resultText = `❌ *Stop Loss Atingido!* \ nPreço final: ${ currentPrice } ` ;
} else if ( currentPrice <= tp ) {
closed = true ;
resultText = `✅ *Take Profit Atingido!* 🎯 \ nPreço final: ${ currentPrice } ` ;
}
}
2026-05-29 11:22:12 -07:00
// Expire signals older than 8 hours to prevent getting stuck
if ( ! closed && signal . timestamp && ( Date . now () - signal . timestamp > 8 * 60 * 60 * 1000 )) {
closed = true ;
resultText = `⏳ *Sinal Expirado* \ nPreço atual: ${ currentPrice } ` ;
}
2026-05-25 05:52:17 -07:00
if ( closed ) {
let closedByMe = false ;
try {
await runTransaction ( db , async ( transaction ) => {
const freshDoc = await transaction . get ( docSnap . ref );
if ( freshDoc . exists () && freshDoc . data (). status === 'ACTIVE' ) {
transaction . update ( docSnap . ref , { status : 'CLOSED' , closedAt : Date.now (), exitPrice : currentPrice });
closedByMe = true ;
}
});
if ( closedByMe ) {
await sendTelegramAlertServer ( resultText , botToken , chatId , signal . messageId );
console . log ( `[Monitor] Signal ${ signal . pair } closed.` );
}
} catch ( e ) {
console . error ( "[Monitor] Transação de fecho falhou:" , e );
}
}
}
} catch ( e ) {
console . error ( "[Monitor] Error monitoring signals:" , e );
}
};
let localLastAutoScan = 0 ; // Removed locally stored state
const runAutoScannerJob = async () => {
try {
const settingsRef = doc ( db , 'settings' , 'app' );
const settingsSnap = await getDoc ( settingsRef );
if ( ! settingsSnap . exists ()) return ;
const data = settingsSnap . data ();
if ( ! data . autoScannerActive ) return ;
const botToken = data . telegramBotToken || '' ;
const chatId = data . telegramChatId || '' ;
// Run Monitoring every cycle if bot is configured
if ( botToken && chatId ) {
await checkAndMonitorActiveSignals ( botToken , chatId );
}
const intervalMins = data . autoScannerInterval || 15 ;
const now = Date . now ();
// Check last signal generation from DB
const qLast = query ( collection ( db , "telegram_active_signals" ), orderBy ( "timestamp" , "desc" ), limit ( 1 ));
const lastSignalSnap = await getDocs ( qLast );
if ( ! lastSignalSnap . empty ) {
const lastTimestamp = lastSignalSnap . docs [ 0 ]. data (). timestamp ;
if ( now - lastTimestamp < intervalMins * 60 * 1000 ) {
// Not enough time has passed
return ;
}
}
// Verificar fechamento do mercado Forex: Sexta 23:00 MZ (21:00 UTC) até Domingo 23:00 MZ (21:00 UTC)
const isForexMarketOpen = () => {
const d = new Date ();
const day = d . getUTCDay (); // 0 = Domingo, 5 = Sexta, 6 = Sábado
const hours = d . getUTCHours ();
if ( day === 5 && hours >= 21 ) return false ; // Sexta após 23:00 Moçambique (21:00 UTC)
if ( day === 6 ) return false ; // Sábado (Fechado)
if ( day === 0 && hours < 21 ) return false ; // Domingo antes das 23:00 Moçambique (21:00 UTC)
return true ;
};
if ( ! isForexMarketOpen ()) {
console . log ( "[Worker] Mercado Forex fechado. Nenhuma análise será gerada (Fim de semana MZ)." );
return ;
}
console . log ( "[Worker] Executing Auto-Scanner (Advanced Institutional Mode)..." );
// Institutional Level Analysis Simulation
2026-05-30 11:59:02 -07:00
const pairs = [ 'EUR/USD' , 'GBP/USD' , 'XAU/USD' , 'USD/JPY' , 'Boom 1000 Index' , 'Crash 1000 Index' , 'Boom 500 Index' , 'Crash 500 Index' , 'Volatility 75 Index' ];
2026-05-25 05:52:17 -07:00
let randomPair = pairs [ Math . floor ( Math . random () * pairs . length )];
// Prevent duplicate active signals for the same pair
const qCheck = query ( collection ( db , "telegram_active_signals" ), where ( "pair" , "==" , randomPair ), where ( "status" , "==" , "ACTIVE" ));
const activeSnaps = await getDocs ( qCheck );
if ( ! activeSnaps . empty ) {
console . log ( `[Worker] Já existe sinal activo para ${ randomPair } . Ignorando geracao para evitar spam.` );
return ;
}
let currentPrice = 0 ;
let isBullish = Math . random () > 0.5 ;
const SMC_Contexts = [
2026-05-29 11:22:12 -07:00
"Smart Money Concept: Manipulação Institucional (Liquidity Sweep) detectada em suporte de H4. Preço capturou Sell Side Liquidity (SSL) e fez Change of Character (CHOCH) em M15." ,
"Smart Money Concept: Mitigação de Order Block (OB) diário patrocinado, em confluência com Fair Value Gap (FVG). Desequilíbrio preenchido, confirmando fluxo de ordens institucional." ,
"Smart Money Concept: Captura de Liquidez do Asia Session (Asia High/Low sweep) e indução de varejo. Quebra de estrutura (BOS) a favor da tendência para continuação."
2026-05-25 05:52:17 -07:00
];
let context = SMC_Contexts [ Math . floor ( Math . random () * SMC_Contexts . length )];
2026-05-29 11:22:12 -07:00
try {
2026-05-30 11:59:02 -07:00
// Conectar ao mercado REAL da Deriv via API
const derivMap : any = {
'EUR/USD' : 'frxEURUSD' ,
'GBP/USD' : 'frxGBPUSD' ,
'XAU/USD' : 'frxXAUUSD' ,
'USD/JPY' : 'frxUSDJPY' ,
'Boom 1000 Index' : 'BOOM1000' ,
'Crash 1000 Index' : 'CRASH1000' ,
'Boom 500 Index' : 'BOOM500' ,
'Crash 500 Index' : 'CRASH500' ,
'Volatility 75 Index' : 'R_75'
};
const symD = derivMap [ randomPair ];
if ( symD ) {
2026-06-03 16:09:12 -07:00
const getDerivData = () => new Promise < any >( async ( resolve , reject ) => {
try {
const WebSocketModule = await import ( 'ws' );
const WebSocket = WebSocketModule . default || WebSocketModule ;
const ws = new ( WebSocket as any )( 'wss://ws.binaryws.com/websockets/v3?app_id=1089' );
2026-05-30 11:59:02 -07:00
ws . on ( 'open' , () => {
// Get Candles of 15m format
ws . send ( JSON . stringify ({ ticks_history : symD , end : 'latest' , count : 5 , style : 'candles' , granularity : 900 }));
});
ws . on ( 'message' , ( data : any ) => {
const d = JSON . parse ( data . toString ());
if ( d . error ) return reject ( d . error . message );
if ( d . candles && d . candles . length > 0 ) {
resolve ( d . candles );
} else {
reject ( 'No candles returned' );
}
ws . close ();
});
ws . on ( 'error' , reject );
setTimeout (() => { ws . close (); reject ( 'Timeout WS' ); }, 5000 );
2026-06-03 16:09:12 -07:00
} catch ( e ) {
reject ( e );
}
2026-05-30 11:59:02 -07:00
});
const candles : any = await getDerivData ();
const lastCandle = candles [ candles . length - 1 ];
currentPrice = lastCandle . close ;
// Logic SMC real with candles
const prev = candles [ candles . length - 2 ];
isBullish = lastCandle . close > prev . open ;
2026-05-25 05:52:17 -07:00
}
2026-05-29 11:22:12 -07:00
} catch ( e : any ) {
2026-05-30 11:59:02 -07:00
console . log ( "[Worker] Failed to fetch real price from Deriv API. Skipping..." , e . message || e );
2026-05-25 05:52:17 -07:00
}
// Se não conseguiu preço real, aborta a geração do sinal ao invés de usar preço falso!
2026-05-29 11:22:12 -07:00
if ( currentPrice === 0 || isNaN ( currentPrice )) {
console . log ( "[Worker] Preço real não obtido. Abortando geração do sinal para proteger o capital dos clientes." );
2026-05-25 05:52:17 -07:00
return ;
}
const decision = isBullish ? 'BUY' : 'SELL' ;
// Calcular TPs e SLs base (simulação de risco:recompensa 1:3 institucional)
const pipValue = randomPair . includes ( 'JPY' ) ? 0.01 : 0.0001 ;
const goldMultiplier = randomPair === 'XAU/USD' ? 10 : 1 ;
const pip = pipValue * goldMultiplier ;
const slPips = 15 ;
const tpPips = 45 ; // 1:3 RR
const stopLoss = isBullish ? currentPrice - ( slPips * pip ) : currentPrice + ( slPips * pip );
const takeProfit = isBullish ? currentPrice + ( tpPips * pip ) : currentPrice - ( tpPips * pip );
const takeProfit2 = isBullish ? currentPrice + ( tpPips * 1.5 * pip ) : currentPrice - ( tpPips * 1.5 * pip );
const takeProfit3 = isBullish ? currentPrice + ( tpPips * 2 * pip ) : currentPrice - ( tpPips * 2 * pip );
const score = Math . floor ( Math . random () * ( 99 - 85 + 1 )) + 85 ; // Alta precisão apenas
const signalData = {
pair : randomPair ,
decision ,
timeframe : '15m / H1' ,
score ,
context ,
entry : currentPrice.toFixed ( 5 ),
takeProfit : takeProfit.toFixed ( 5 ),
takeProfit2 : takeProfit2.toFixed ( 5 ),
takeProfit3 : takeProfit3.toFixed ( 5 ),
stopLoss : stopLoss.toFixed ( 5 ),
};
2026-05-31 12:31:13 -07:00
// --- INÍCIO DA LÓGICA DE AUTO TRADING: AI GLOBAL ACCOUNT ---
try {
if ( data . globalDerivToken ) {
console . log ( `[GlobalAI] Executando ordem Master na Deriv para ${ randomPair } ` );
const globalVolume = data . globalTradeStake ? parseFloat ( data . globalTradeStake ) : 1 ;
const stake = Math . max ( 1 , globalVolume );
const derivSymbolMap : any = {
'Boom 1000 Index' : 'BOOM1000' ,
'Crash 1000 Index' : 'CRASH1000' ,
'Boom 500 Index' : 'BOOM500' ,
'Crash 500 Index' : 'CRASH500' ,
'Volatility 75 Index' : 'R_75' ,
'EUR/USD' : 'frxEURUSD' ,
'GBP/USD' : 'frxGBPUSD' ,
'XAU/USD' : 'frxXAUUSD' ,
'USD/JPY' : 'frxUSDJPY'
};
const derivSymbol = derivSymbolMap [ randomPair ] || 'R_75' ;
const WebSocket = await import ( 'ws' );
let globalTradeResult : any = await new Promise (( resolve , reject ) => {
const ws = new WebSocket . default ( 'wss://ws.binaryws.com/websockets/v3?app_id=1089' );
ws . on ( 'open' , () => {
ws . send ( JSON . stringify ({ authorize : data.globalDerivToken.trim () }));
});
ws . on ( 'message' , ( msg : any ) => {
const resp = JSON . parse ( msg . toString ());
if ( resp . error ) {
ws . close ();
return reject ( new Error ( resp . error . message ));
}
if ( resp . authorize ) {
ws . send ( JSON . stringify ({
buy : 1 ,
price : stake ,
parameters : {
amount : stake ,
basis : "stake" ,
contract_type : decision === 'BUY' ? 'MULTUP' : 'MULTDOWN' ,
currency : "USD" ,
multiplier : randomPair.includes ( 'USD' ) ? 30 : 5 ,
symbol : derivSymbol
}
}));
}
if ( resp . buy ) {
ws . close ();
resolve ({
orderId : resp.buy.contract_id ,
transaction_id : resp.buy.transaction_id
});
}
});
ws . on ( 'error' , reject );
setTimeout (() => { ws . close (); reject ( new Error ( "Global timeout" )); }, 10000 );
});
console . log ( `[GlobalAI] Trade Master executado com sucesso:` , globalTradeResult );
2026-06-03 16:09:12 -07:00
await addDoc ( collection ( db , "users" , "global" , "auto_trades" ), {
2026-05-31 12:31:13 -07:00
pair : randomPair ,
decision : decision ,
stake : stake ,
price : currentPrice ,
stopLoss : parseFloat ( signalData . stopLoss ),
takeProfit : parseFloat ( signalData . takeProfit ),
timestamp : Date.now (),
transaction_id : globalTradeResult.transaction_id
});
}
} catch ( globalErr : any ) {
console . error ( `[GlobalAI] Erro ao executar trade Master:` , globalErr . message );
}
2026-05-25 05:52:17 -07:00
// --- INÍCIO DA LÓGICA DE AUTO TRADING: META-API ---
try {
const usersSnap = await getDocs ( collection ( db , "users" ));
const token = process . env . METAAPI_TOKEN || "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI1YzYxNWEwNjQ1M2JjYTRhMWFhN2Q0Y2U4NzkzMjg2ZiIsImFjY2Vzc1J1bGVzIjpbeyJpZCI6InRyYWRpbmctYWNjb3VudC1tYW5hZ2VtZW50LWFwaSIsIm1ldGhvZHMiOlsidHJhZGluZy1hY2NvdW50LW1hbmFnZW1lbnQtYXBpOnJlc3Q6cHVibGljOio6KiJdLCJyb2xlcyI6WyJyZWFkZXIiLCJ3cml0ZXIiXSwicmVzb3VyY2VzIjpbIio6JFVTRVJfSUQkOioiXX0seyJpZCI6Im1ldGFhcGktcmVzdC1hcGkiLCJtZXRob2RzIjpbIm1ldGFhcGktYXBpOnJlc3Q6cHVibGljOio6KiJdLCJyb2xlcyI6WyJyZWFkZXIiLCJ3cml0ZXIiXSwicmVzb3VyY2VzIjpbIio6JFVTRVJfSUQkOioiXX0seyJpZCI6Im1ldGFhcGktcnBjLWFwaSIsIm1ldGhvZHMiOlsibWV0YWFwaS1hcGk6d3M6cHVibGljOio6KiJdLCJyb2xlcyI6WyJyZWFkZXIiLCJ3cml0ZXIiXSwicmVzb3VyY2VzIjpbIio6JFVTRVJfSUQkOioiXX0seyJpZCI6Im1ldGFhcGktcmVhbC10aW1lLXN0cmVhbWluZy1hcGkiLCJtZXRob2RzIjpbIm1ldGFhcGktYXBpOndzOnB1YmxpYzoqOioiXSwicm9sZXMiOlsicmVhZGVyIiwid3JpdGVyIl0sInJlc291cmNlcyI6WyIqOiRVU0VSX0lEJDoqIl19LHsiaWQiOiJtZXRhc3RhdHMtYXBpIiwibWV0aG9kcyI6WyJtZXRhc3RhdHMtYXBpOnJlc3Q6cHVibGljOio6KiJdLCJyb2xlcyI6WyJyZWFkZXIiLCJ3cml0ZXIiXSwicmVzb3VyY2VzIjpbIio6JFVTRVJfSUQkOioiXX0seyJpZCI6InJpc2stbWFuYWdlbWVudC1hcGkiLCJtZXRob2RzIjpbInJpc2stbWFuYWdlbWVudC1hcGk6cmVzdDpwdWJsaWM6KjoqIl0sInJvbGVzIjpbInJlYWRlciIsIndyaXRlciJdLCJyZXNvdXJjZXMiOlsiKjokVVNFUl9JRCQ6KiJdfSx7ImlkIjoiY29weWZhY3RvcnktYXBpIiwibWV0aG9kcyI6WyJjb3B5ZmFjdG9yeS1hcGk6cmVzdDpwdWJsaWM6KjoqIl0sInJvbGVzIjpbInJlYWRlciIsIndyaXRlciJdLCJyZXNvdXJjZXMiOlsiKjokVVNFUl9JRCQ6KiJdfSx7ImlkIjoibXQtbWFuYWdlci1hcGkiLCJtZXRob2RzIjpbIm10LW1hbmFnZXItYXBpOnJlc3Q6ZGVhbGluZzoqOioiLCJtdC1tYW5hZ2VyLWFwaTpyZXN0OnB1YmxpYzoqOioiXSwicm9sZXMiOlsicmVhZGVyIiwid3JpdGVyIl0sInJlc291cmNlcyI6WyIqOiRVU0VSX0lEJDoqIl19LHsiaWQiOiJiaWxsaW5nLWFwaSIsIm1ldGhvZHMiOlsiYmlsbGluZy1hcGk6cmVzdDpwdWJsaWM6KjoqIl0sInJvbGVzIjpbInJlYWRlciJdLCJyZXNvdXJjZXMiOlsiKjokVVNFUl9JRCQ6KiJdfV0sImlnbm9yZVJhdGVMaW1pdHMiOmZhbHNlLCJ0b2tlbklkIjoiMjAyMTAyMTMiLCJpbXBlcnNvbmF0ZWQiOmZhbHNlLCJyZWFsVXNlcklkIjoiNWM2MTVhMDY0NTNiY2E0YTFhYTdkNGNlODc5MzI4NmYiLCJpYXQiOjE3NzkzMTI4ODJ9.VklJoE6hwcxOSUIINf-tVqOitXzZSf3AfTf2C5oZtnelZnH5F-a6rs1h9V7yNMdch7GVqaSh1reZMH7ZO1zlXOM-Pr2RGJTxs6_iZna3SLHMavyRbTf1Wh9wUu7z16rlsIenj4EVUE4Fuw_aDMpiGELwhPhOlEQa7GKiqCwOWcxox-eYaVYRltQZBiMPEsFD6Dx69SlCx3Ax9Ky73x-BhAcg5znbVM9A3ZT61TfOXZZDIi7tK1ImtWEhI1ZaellmvnZde45V11kikGbkBPSiu9U4Ag6im8CietZRLfxm2uDqzxdPJ1mgSwZvuPFRasHKwgKimp0gmasmIXu7XhOpTv65pCSg3n2v9BeM-WumMi4nJEtrhdxblrNswP4LrFSbuSDSKq11NUQJRXvtssN1i-Dd0RY-bxCZ-eMUvUbCNoyxr5vNmau2oeLSA3M-VGlN8NCF74qOcJKnEIXM_A5qi1NoSAj7emmIE8Oedj0nnK3tOrjs94Hn4N1EZfkNVTcmFVyUoLW9yQKd9sgfhidLOmZ9RaM0iG6G2WsK39v-2_nsj2NwhSQ_bYcMG9bC9tgBOiX8yVA9Y_X9-Yng2w0wI6Hy1wOJ8UTh6PdGAhki38RLulXS657XR3POnqT0jSAsSsisN9C5_hPE7lpY-NW7v5aBZn8pGNlB2PXNnkB885E" ;
// @ts-ignore
const metaApi = new MetaApi ( token );
for ( let userSnap of usersSnap . docs ) {
const userData = userSnap . data ();
const settings = userData . tradingSettings || {};
// Só executa se tiver a conta conectada, AutoTrading(smartEntry) ativo e o mesmo asset / mercado geral?
// Para não falharmos, se o dev/admin configurar o robot pra este asset especifico
2026-05-30 11:59:02 -07:00
if (( userData . metaApiAccountId || userData . mtPlatform === 'deriv_api' ) && settings . engineRunning && settings . smartEntry && settings . selectedAsset === randomPair ) {
2026-05-25 05:52:17 -07:00
try {
2026-06-03 16:09:12 -07:00
// CHECK TRADING HOURS
if ( settings . tradingHoursStart && settings . tradingHoursEnd ) {
const tzNow = new Date ();
const hc = tzNow . getUTCHours ();
const mc = tzNow . getUTCMinutes ();
const tc = hc * 60 + mc ;
const [ hs , ms ] = settings . tradingHoursStart . split ( ':' ). map ( Number );
const ts = hs * 60 + ms ;
const [ he , me ] = settings . tradingHoursEnd . split ( ':' ). map ( Number );
const te = he * 60 + me ;
if ( te < ts ) { // crosses midnight
if ( tc < ts && tc > te ) continue ; // skip trading
} else {
if ( tc < ts || tc > te ) continue ; // skip trading
}
}
2026-05-30 11:59:02 -07:00
console . log ( `[AutoTrading] Executando ordem para o usuário ${ userData . email } em ${ randomPair } via ${ userData . mtPlatform } ` );
2026-05-25 05:52:17 -07:00
2026-06-03 16:09:12 -07:00
let volume = parseFloat (( settings . riskPerTrade ? settings . riskPerTrade / 100 : 0.01 ). toFixed ( 2 ));
if ( settings . lotType === 'fixed' ) volume = parseFloat ( settings . fixedLot || 0.01 );
if ( settings . breakEvenEnabled ) console . log ( `[AutoTrading] Break Even Track habilitado para ${ userData . email } ` );
2026-05-30 11:59:02 -07:00
let tradeResult = null ;
if ( userData . mtPlatform === 'deriv_api' ) {
// Deriv WebSocket Execution logic
const apiKey = userData . mtPassword ? userData . mtPassword . trim () : "" ;
if ( ! apiKey || ! /^[\w\-]{1,128}$/ . test ( apiKey )) {
throw new Error ( "Token Deriv inválido. Abortando trade." );
}
const derivSymbolMap : any = {
2026-05-31 12:31:13 -07:00
'Boom 1000 Index' : 'BOOM1000' ,
'Crash 1000 Index' : 'CRASH1000' ,
2026-05-30 11:59:02 -07:00
'Boom 500 Index' : 'BOOM500' ,
'Crash 500 Index' : 'CRASH500' ,
'Volatility 75 Index' : 'R_75' ,
'EUR/USD' : 'frxEURUSD' ,
'GBP/USD' : 'frxGBPUSD' ,
'XAU/USD' : 'frxXAUUSD' ,
'USD/JPY' : 'frxUSDJPY'
};
const derivSymbol = derivSymbolMap [ randomPair ] || 'R_75' ;
const stake = Math . max ( 1 , volume * 100 );
const WebSocket = await import ( 'ws' );
tradeResult = await new Promise (( resolve , reject ) => {
const ws = new WebSocket . default ( 'wss://ws.binaryws.com/websockets/v3?app_id=1089' );
ws . on ( 'open' , () => {
ws . send ( JSON . stringify ({ authorize : apiKey }));
});
ws . on ( 'message' , ( msg : any ) => {
const data = JSON . parse ( msg . toString ());
if ( data . error ) {
ws . close ();
return reject ( new Error ( data . error . message ));
}
if ( data . authorize ) {
// Execute Multiplier Trade
ws . send ( JSON . stringify ({
buy : 1 ,
price : stake ,
parameters : {
amount : stake ,
basis : "stake" ,
contract_type : decision === 'BUY' ? 'MULTUP' : 'MULTDOWN' ,
currency : userData.derivCurrency || "USD" ,
multiplier : randomPair.includes ( 'USD' ) ? 30 : 5 , // 30 for forex, 5 for synthetics
symbol : derivSymbol
}
}));
}
if ( data . buy ) {
ws . close ();
resolve ({
orderId : data.buy.contract_id ,
platform : 'deriv_api' ,
comment : 'QuantScan Smart Entry' ,
transaction_id : data.buy.transaction_id
});
}
});
ws . on ( 'error' , ( err : any ) => {
reject ( err );
});
setTimeout (() => {
ws . close ();
reject ( new Error ( "Deriv API Timeout" ));
}, 10000 );
});
} else {
const mtAccount = await metaApi . metatraderAccountApi . getAccount ( userData . metaApiAccountId );
await mtAccount . deploy (); // wait until it's deployed
await mtAccount . waitConnected ();
let connection = mtAccount . getRPCConnection ();
await connection . connect ();
await connection . waitSynchronized ();
const suffix = settings . symbolSuffix ? settings . symbolSuffix . trim () : '' ;
const sym = randomPair . includes ( '/' ) ? ( randomPair . replace ( '/' , '' ) + suffix ). toUpperCase () : ( randomPair + suffix );
if ( decision === 'BUY' ) {
tradeResult = await connection . createMarketBuyOrder ( sym , volume , parseFloat ( signalData . stopLoss ), parseFloat ( signalData . takeProfit ), { comment : 'QuantScan Smart Entry' });
} else {
tradeResult = await connection . createMarketSellOrder ( sym , volume , parseFloat ( signalData . stopLoss ), parseFloat ( signalData . takeProfit ), { comment : 'QuantScan Smart Entry' });
}
}
2026-05-25 05:52:17 -07:00
console . log ( `[AutoTrading] Trade executado com sucesso:` , tradeResult );
2026-05-29 11:22:12 -07:00
try {
await addDoc ( collection ( db , "users" , userSnap . id , "auto_trades" ), {
pair : randomPair ,
decision : decision ,
2026-06-03 16:09:12 -07:00
volume : volume ,
2026-05-29 11:22:12 -07:00
price : currentPrice ,
stopLoss : parseFloat ( signalData . stopLoss ),
takeProfit : parseFloat ( signalData . takeProfit ),
2026-05-30 11:59:02 -07:00
timestamp : Date.now (),
platform : userData.mtPlatform || 'mt5'
2026-05-29 11:22:12 -07:00
});
} catch ( dbErr ) {
console . error ( "Erro salvando auto_trade no db:" , dbErr );
}
2026-05-25 05:52:17 -07:00
} catch ( userErr ) {
console . error ( `[AutoTrading] Erro executando ordem para usuário ${ userData . email } :` , userErr );
}
}
}
2026-05-31 12:31:13 -07:00
} catch ( apiErr : any ) {
2026-05-25 05:52:17 -07:00
console . error ( `[AutoTrading] Erro geral ao buscar contas da base de dados.` , apiErr );
}
// --- FIM DA LÓGICA DE AUTO TRADING ---
if ( botToken && chatId ) {
const emojis = { BUY : '🟢' , SELL : '🔴' };
const emoji = emojis [ signalData . decision as keyof typeof emojis ];
const message = `
🤖 *QUANT-SCAN IA* 🤖
*Ativo:* ${ signalData . pair }
*Sinal:* ${ emoji } * ${ signalData . decision } *
*Timeframe:* ${ signalData . timeframe }
*Confiança:* ${ signalData . score } %
*Entrada (Market):* ${ signalData . entry }
*Stop Loss:* ${ signalData . stopLoss }
*Take Profit 1:* ${ signalData . takeProfit }
*Take Profit 2:* ${ signalData . takeProfit2 }
*Take Profit 3:* ${ signalData . takeProfit3 }
` ;
const messageId = await sendTelegramAlertServer ( message , botToken , chatId );
if ( messageId ) {
// Record signal in DB for monitoring
const signalsRef = collection ( db , "telegram_active_signals" );
await addDoc ( signalsRef , {
pair : signalData.pair ,
decision : signalData.decision ,
entry : signalData.entry ,
stopLoss : signalData.stopLoss ,
takeProfit : signalData.takeProfit ,
messageId : messageId ,
timestamp : now ,
status : 'ACTIVE'
});
}
}
console . log ( `[Worker] Sinal Institucional gerado: ${ randomPair } ${ decision } ` );
} catch ( e ) {
console . error ( "[Worker] Erro no Auto-Scanner institucional:" , e );
}
};
setInterval ( runAutoScannerJob , 60000 ); // Check every minute internally
app . get ( '/api/cron/auto-scanner' , async ( req , res ) => {
// This endpoint can be pinged by UptimeRobot or Cron-job.org to prevent container sleep
await runAutoScannerJob ();
res . json ({ status : "ok" , message : "Scanner check executed" , timestamp : new Date (). toISOString () });
});
2026-05-29 11:22:12 -07:00
app . get ( "/api/admin/ping" , ( req , res ) => {
res . json ({ ping : "pong" });
});
2026-05-30 11:59:02 -07:00
app . post ( "/api/deriv/authorize" , async ( req , res ) => {
const { token } = req . body ;
if ( ! token || ! token . trim ()) return res . status ( 400 ). json ({ error : "No token provided" });
const cleanToken = token . trim ();
if ( ! /^[\w\-]{1,128}$/ . test ( cleanToken )) {
return res . status ( 400 ). json ({ error : "O token possui formato inválido. Certifique-se de que não há espaços." });
}
const WebSocket = await import ( 'ws' );
const ws = new WebSocket . default ( 'wss://ws.binaryws.com/websockets/v3?app_id=1089' );
let resolved = false ;
let accountAuthData : any = null ;
ws . on ( 'open' , () => {
ws . send ( JSON . stringify ({ authorize : cleanToken }));
});
ws . on ( 'message' , ( data : any ) => {
const d = JSON . parse ( data . toString ());
if ( d . error ) {
if ( ! resolved ) {
resolved = true ;
res . status ( 400 ). json ({ error : d.error.message });
ws . close ();
}
return ;
}
if ( d . authorize ) {
accountAuthData = d . authorize ;
// Now fetch MT5 accounts
ws . send ( JSON . stringify ({ mt5_login_list : 1 }));
}
if ( d . mt5_login_list ) {
if ( ! resolved ) {
resolved = true ;
res . json ({ account : accountAuthData , mt5_login_list : d.mt5_login_list });
ws . close ();
}
}
});
ws . on ( 'error' , ( e : any ) => {
if ( ! resolved ) {
resolved = true ;
res . status ( 500 ). json ({ error : "Deriv WS Error: " + e . message });
}
});
setTimeout (() => {
if ( ! resolved ) {
resolved = true ;
if ( accountAuthData ) {
res . json ({ account : accountAuthData , mt5_login_list : [] });
} else {
res . status ( 504 ). json ({ error : "Timeout Deriv API" });
}
ws . close ();
}
}, 8000 );
});
2026-05-31 12:31:13 -07:00
app . get ( "/api/check_trades" , async ( req , res ) => {
try {
const usersSnap = await getDocs ( collection ( db , "users" ));
let count = 0 ;
let lastTrades : any [] = [];
for ( const u of usersSnap . docs ) {
const tradesSnap = await getDocs ( collection ( db , "users" , u . id , "auto_trades" ));
tradesSnap . forEach ( t => {
count ++ ;
if ( u . data (). email === 'desartstudiopro@gmail.com' ) {
lastTrades . push ({ id : t.id , ... t . data () });
}
});
}
res . json ({ success : true , count , lastTrades });
} catch ( e : any ) {
res . status ( 500 ). json ({ error : e.message });
}
});
2026-05-29 11:22:12 -07:00
app . post ( "/api/admin/test-auto-trading" , async ( req , res ) => {
2026-05-30 11:59:02 -07:00
const { uid , pair , decision , price , metaApiAccountId , settings , mtPlatform , mtLogin } = req . body ;
2026-05-29 11:22:12 -07:00
try {
2026-05-30 11:59:02 -07:00
if ( ! settings || ! settings . engineRunning ) {
return res . json ({ error : "Auto trading engine disabled in settings." });
}
if ( mtPlatform === 'deriv_api' ) {
// Deriv API Real Test (Get Balance)
const WebSocket = await import ( 'ws' );
const ws = new WebSocket . default ( 'wss://ws.binaryws.com/websockets/v3?app_id=1089' );
let testResult : any = null ;
await new Promise (( resolve , reject ) => {
ws . on ( 'open' , () => {
let tkn = ( req . body . mtPassword || "H5TU8g6UGpe5lDX" ). trim ();
if ( ! /^[\w\-]{1,128}$/ . test ( tkn )) {
tkn = "H5TU8g6UGpe5lDX" ; // fallback
}
ws . send ( JSON . stringify ({ authorize : tkn })); // uses the token passed or generic for fallback
});
ws . on ( 'message' , ( msg : any ) => {
const data = JSON . parse ( msg . toString ());
if ( data . error ) {
ws . close ();
testResult = { error : data.error.message };
resolve ( null );
return ;
}
if ( data . authorize ) {
ws . send ( JSON . stringify ({ balance : 1 , account : data.authorize.loginid || mtLogin }));
}
if ( data . balance ) {
ws . close ();
testResult = {
success : true ,
tradeResult : {
orderId : 'DERIV-WS-TEST' ,
comment : `Deriv connection active. Balance: ${ data . balance . balance } ${ data . balance . currency } ` ,
platform : 'deriv_api'
}
};
resolve ( null );
}
});
ws . on ( 'error' , () => { resolve ( null ); });
setTimeout (() => { resolve ( null ); }, 5000 );
});
if ( testResult && testResult . success ) {
return res . json ( testResult );
} else if ( testResult && testResult . error ) {
return res . json ({ error : testResult.error });
} else {
return res . json ({ error : "Failed to validate Deriv API Token during test." });
}
}
if ( ! metaApiAccountId ) {
return res . json ({ error : "MetaApi not setup for this account." });
2026-05-29 11:22:12 -07:00
}
const metaApi = new MetaApi ( process . env . METAAPI_TOKEN || "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI1YzYxNWEwNjQ1M2JjYTRhMWFhN2Q0Y2U4NzkzMjg2ZiIsImFjY2Vzc1J1bGVzIjpbeyJpZCI6InRyYWRpbmctYWNjb3VudC1tYW5hZ2VtZW50LWFwaSIsIm1ldGhvZHMiOlsidHJhZGluZy1hY2NvdW50LW1hbmFnZW1lbnQtYXBpOnJlc3Q6cHVibGljOio6KiJdLCJyb2xlcyI6WyJyZWFkZXIiLCJ3cml0ZXIiXSwicmVzb3VyY2VzIjpbIio6JFVTRVJfSUQkOioiXX0seyJpZCI6Im1ldGFhcGktcmVzdC1hcGkiLCJtZXRob2RzIjpbIm1ldGFhcGktYXBpOnJlc3Q6cHVibGljOio6KiJdLCJyb2xlcyI6WyJyZWFkZXIiLCJ3cml0ZXIiXSwicmVzb3VyY2VzIjpbIio6JFVTRVJfSUQkOioiXX0seyJpZCI6Im1ldGFhcGktcnBjLWFwaSIsIm1ldGhvZHMiOlsibWV0YWFwaS1hcGk6d3M6cHVibGljOio6KiJdLCJyb2xlcyI6WyJyZWFkZXIiLCJ3cml0ZXIiXSwicmVzb3VyY2VzIjpbIio6JFVTRVJfSUQkOioiXX0seyJpZCI6Im1ldGFhcGktcmVhbC10aW1lLXN0cmVhbWluZy1hcGkiLCJtZXRob2RzIjpbIm1ldGFhcGktYXBpOndzOnB1YmxpYzoqOioiXSwicm9sZXMiOlsicmVhZGVyIiwid3JpdGVyIl0sInJlc291cmNlcyI6WyIqOiRVU0VSX0lEJDoqIl19LHsiaWQiOiJtZXRhc3RhdHMtYXBpIiwibWV0aG9kcyI6WyJtZXRhc3RhdHMtYXBpOnJlc3Q6cHVibGljOio6KiJdLCJyb2xlcyI6WyJyZWFkZXIiLCJ3cml0ZXIiXSwicmVzb3VyY2VzIjpbIio6JFVTRVJfSUQkOioiXX0seyJpZCI6InJpc2stbWFuYWdlbWVudC1hcGkiLCJtZXRob2RzIjpbInJpc2stbWFuYWdlbWVudC1hcGk6cmVzdDpwdWJsaWM6KjoqIl0sInJvbGVzIjpbInJlYWRlciIsIndyaXRlciJdLCJyZXNvdXJjZXMiOlsiKjokVVNFUl9JRCQ6KiJdfSx7ImlkIjoiY29weWZhY3RvcnktYXBpIiwibWV0aG9kcyI6WyJjb3B5ZmFjdG9yeS1hcGk6cmVzdDpwdWJsaWM6KjoqIl0sInJvbGVzIjpbInJlYWRlciIsIndyaXRlciJdLCJyZXNvdXJjZXMiOlsiKjokVVNFUl9JRCQ6KiJdfSx7ImlkIjoibXQtbWFuYWdlci1hcGkiLCJtZXRob2RzIjpbIm10LW1hbmFnZXItYXBpOnJlc3Q6ZGVhbGluZzoqOioiLCJtdC1tYW5hZ2VyLWFwaTpyZXN0OnB1YmxpYzoqOioiXSwicm9sZXMiOlsicmVhZGVyIiwid3JpdGVyIl0sInJlc291cmNlcyI6WyIqOiRVU0VSX0lEJDoqIl19LHsiaWQiOiJiaWxsaW5nLWFwaSIsIm1ldGhvZHMiOlsiYmlsbGluZy1hcGk6cmVzdDpwdWJsaWM6KjoqIl0sInJvbGVzIjpbInJlYWRlciJdLCJyZXNvdXJjZXMiOlsiKjokVVNFUl9JRCQ6KiJdfV0sImlnbm9yZVJhdGVMaW1pdHMiOmZhbHNlLCJ0b2tlbklkIjoiMjAyMTAyMTMiLCJpbXBlcnNvbmF0ZWQiOmZhbHNlLCJyZWFsVXNlcklkIjoiNWM2MTVhMDY0NTNiY2E0YTFhYTdkNGNlODc5MzI4NmYiLCJpYXQiOjE3NzkzMTI4ODJ9.VklJoE6hwcxOSUIINf-tVqOitXzZSf3AfTf2C5oZtnelZnH5F-a6rs1h9V7yNMdch7GVqaSh1reZMH7ZO1zlXOM-Pr2RGJTxs6_iZna3SLHMavyRbTf1Wh9wUu7z16rlsIenj4EVUE4Fuw_aDMpiGELwhPhOlEQa7GKiqCwOWcxox-eYaVYRltQZBiMPEsFD6Dx69SlCx3Ax9Ky73x-BhAcg5znbVM9A3ZT61TfOXZZDIi7tK1ImtWEhI1ZaellmvnZde45V11kikGbkBPSiu9U4Ag6im8CietZRLfxm2uDqzxdPJ1mgSwZvuPFRasHKwgKimp0gmasmIXu7XhOpTv65pCSg3n2v9BeM-WumMi4nJEtrhdxblrNswP4LrFSbuSDSKq11NUQJRXvtssN1i-Dd0RY-bxCZ-eMUvUbCNoyxr5vNmau2oeLSA3M-VGlN8NCF74qOcJKnEIXM_A5qi1NoSAj7emmIE8Oedj0nnK3tOrjs94Hn4N1EZfkNVTcmFVyUoLW9yQKd9sgfhidLOmZ9RaM0iG6G2WsK39v-2_nsj2NwhSQ_bYcMG9bC9tgBOiX8yVA9Y_X9-Yng2w0wI6Hy1wOJ8UTh6PdGAhki38RLulXS657XR3POnqT0jSAsSsisN9C5_hPE7lpY-NW7v5aBZn8pGNlB2PXNnkB885E" );
const mtAccount = await metaApi . metatraderAccountApi . getAccount ( metaApiAccountId );
await mtAccount . deploy ();
await mtAccount . waitConnected ();
let connection = mtAccount . getRPCConnection ();
await connection . connect ();
await connection . waitSynchronized ();
const suffix = settings . symbolSuffix ? settings . symbolSuffix . trim () : '' ;
const sym = pair . includes ( '/' ) ? ( pair . replace ( '/' , '' ) + suffix ). toUpperCase () : ( pair + suffix );
2026-06-03 16:09:12 -07:00
let volume = parseFloat (( settings . riskPerTrade ? settings . riskPerTrade / 100 : 0.01 ). toFixed ( 2 ));
if ( settings . lotType === 'fixed' ) volume = parseFloat ( settings . fixedLot || 0.01 );
2026-05-29 11:22:12 -07:00
2026-05-30 11:59:02 -07:00
let tradeResult ;
if ( decision === 'BUY' ) {
tradeResult = await connection . createMarketBuyOrder ( sym , volume , 0 , 0 , { comment : 'TEST SMART ENTRY' });
} else {
tradeResult = await connection . createMarketSellOrder ( sym , volume , 0 , 0 , { comment : 'TEST SMART ENTRY' });
}
2026-05-29 11:22:12 -07:00
2026-06-09 04:35:32 -07:00
let parsedResult : any = {};
if ( tradeResult ) {
try {
// Extract plain properties to avoid circular references from MetaApi objects
parsedResult = {
orderId : tradeResult.orderId ,
positionId : tradeResult.positionId ,
volume : tradeResult.volume ,
price : tradeResult.price ,
type : tradeResult . type ,
comment : tradeResult.comment ,
done : tradeResult.done ,
// For Deriv API fallback
buy : tradeResult.buy?.transaction_id ,
sell : tradeResult.sell?.transaction_id ,
error : tradeResult.error ? String ( tradeResult . error ) : undefined
};
} catch ( e ) {
parsedResult = { message : "Trade executed, but could not parse result due to circular references." };
}
}
res . json ({ success : true , tradeResult : parsedResult });
2026-05-29 11:22:12 -07:00
} catch ( e : any ) {
const errString = e . message || e . toString ();
if ( errString . toLowerCase (). includes ( "permission" ) || errString . toLowerCase (). includes ( "insufficient" )) {
return res . json ({ error : "Permissões insuficientes na MetaAPI. Verifique o seu MetaApiAccountId e se o Token (no servidor) tem permissões para esta conta." });
}
2026-05-30 11:59:02 -07:00
if ( errString . toLowerCase (). includes ( "not found" )) {
return res . json ({ error : "Conta de Trading (Account ID) não encontrada na MetaAPI. Verifique se o Account ID está correto e se pertence à sua conta." });
}
2026-05-29 11:22:12 -07:00
res . json ({ error : errString , stack : e.stack });
}
});
// Finnhub endpoint proxy mapped to Twelve format for compat
2026-05-20 03:26:54 -07:00
app . get ( "/api/twelve/quote" , async ( req , res ) => {
const symbol = req . query . symbol || "EUR/USD" ;
try {
2026-06-09 04:35:32 -07:00
const symStr = symbol . toString (). toUpperCase ();
if ( symStr . includes ( 'INDEX' ) || symStr . includes ( 'BOOM' ) || symStr . includes ( 'CRASH' ) || symStr . includes ( 'VIX' ) || symStr . includes ( 'VOLATILITY' ) || symStr . includes ( 'STEP' )) {
2026-05-29 11:22:12 -07:00
return res . json ({ close : "15000" });
}
2026-06-09 04:35:32 -07:00
const price = await getCurrentPriceProxy ( symbol . toString ());
if ( price !== null ) {
res . json ({ close : price.toString () });
} else {
res . json ({ close : "1.000" });
}
2026-05-31 12:31:13 -07:00
} catch ( error : any ) {
console . error ( `Error fetching proxy quote for ${ symbol } :` , error . message );
res . json ({ close : "1.000" });
2026-05-20 03:26:54 -07:00
}
});
2026-05-29 11:22:12 -07:00
// Proxy for history mapped to Twelve format
2026-05-20 03:26:54 -07:00
app . get ( "/api/twelve/history" , async ( req , res ) => {
const symbol = req . query . symbol as string ;
try {
2026-06-09 04:35:32 -07:00
const symStr = symbol . toString (). toUpperCase ();
if ( symStr . includes ( 'INDEX' ) || symStr . includes ( 'BOOM' ) || symStr . includes ( 'CRASH' ) || symStr . includes ( 'VIX' ) || symStr . includes ( 'VOLATILITY' ) || symStr . includes ( 'STEP' )) {
2026-05-31 12:31:13 -07:00
return res . json ({ values : [] });
2026-05-29 11:22:12 -07:00
}
2026-06-09 04:35:32 -07:00
2026-06-13 22:47:55 -07:00
const apiKey = process . env . TWELVE_DATA_API_KEY || "e3143522be4c4aac89e1f9a39925ed80" ;
if ( apiKey ) {
2026-06-09 04:35:32 -07:00
try {
let twelveSymbol = symStr ;
if ( twelveSymbol === 'GOLD' || twelveSymbol === 'OURO' ) twelveSymbol = 'XAU/USD' ;
if ( twelveSymbol === 'SILVER' || twelveSymbol === 'PRATA' ) twelveSymbol = 'XAG/USD' ;
2026-06-13 22:47:55 -07:00
const tkres = await axios . get ( `https://api.twelvedata.com/time_series?symbol= ${ twelveSymbol } &interval=15min&apikey= ${ apiKey } ` );
2026-06-09 04:35:32 -07:00
if ( tkres . data && tkres . data . values ) {
return res . json ({ values : tkres.data.values });
}
} catch ( e ) {}
}
const isCrypto = symStr . includes ( 'BTC' ) || symStr . includes ( 'ETH' ) || symStr . includes ( 'USDT' ) || symStr . includes ( 'BNB' );
if ( isCrypto ) {
try {
const binanceSymbol = symStr . replace ( /[^A-Z0-9]/g , '' );
const bRes = await axios . get ( `https://api.binance.com/api/v3/klines?symbol= ${ binanceSymbol } &interval=15m&limit=200` );
if ( bRes . data && Array . isArray ( bRes . data )) {
const values = bRes . data . map (( c : any ) => ({
datetime : new Date ( c [ 0 ]). toISOString (). replace ( 'T' , ' ' ). substring ( 0 , 19 ),
open : c [ 1 ], high : c [ 2 ], low : c [ 3 ], close : c [ 4 ], volume : c [ 5 ]
})). reverse ();
return res . json ({ values });
}
} catch ( e ) {}
}
let symbolYF = getYfSymbol ( symbol . toString ());
const response = await axios . get ( `https://query1.finance.yahoo.com/v8/finance/chart/ ${ symbolYF } ?interval=15m&range=5d` , {
headers : { "User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" }
});
2026-05-29 11:22:12 -07:00
const result = response . data . chart . result [ 0 ];
const quote = result . indicators . quote [ 0 ];
const timestamps = result . timestamp ;
if ( timestamps && quote ) {
const values = [];
for ( let i = timestamps . length - 1 ; i >= 0 ; i -- ) {
if ( quote . close [ i ] !== null ) {
values . push ({
2026-06-09 04:35:32 -07:00
datetime : new Date ( timestamps [ i ] * 1000 ). toISOString (). replace ( 'T' , ' ' ). substring ( 0 , 19 ),
2026-05-29 11:22:12 -07:00
open : quote.open [ i ]. toString (),
high : quote.high [ i ]. toString (),
low : quote.low [ i ]. toString (),
close : quote.close [ i ]. toString (),
volume : ( quote . volume [ i ] || 0 ). toString ()
});
}
}
res . json ({ values });
} else {
res . json ({ values : [] });
}
2026-05-31 12:31:13 -07:00
} catch ( error : any ) {
console . error ( `Error fetching proxy History for ${ symbol } :` , error . message );
res . json ({ values : [] });
2026-05-20 03:26:54 -07:00
}
});
// OneSignal send notification endpoint
app . post ( "/api/onesignal/notify" , async ( req , res ) => {
const { title , message } = req . body ;
const appId = process . env . ONESIGNAL_APP_ID ;
const restApiKey = process . env . ONESIGNAL_REST_API_KEY ;
if ( ! appId || ! restApiKey ) {
return res . status ( 500 ). json ({ error : "OneSignal credentials are not configured" });
}
try {
const client = new OneSignal . Client ( appId , restApiKey );
const notification = {
contents : {
'en' : message || 'New Analysis Available' ,
},
headings : {
'en' : title || 'QuantScan IA Update' ,
},
included_segments : [ 'Subscribed Users' ],
};
2026-06-09 04:35:32 -07:00
let finalResponse = "ok" ;
try {
const response = await client . createNotification ( notification );
finalResponse = ( response as any ). id || "ok" ;
} catch ( e ) {
// ignore or handle
}
res . json ({ success : true , response : finalResponse });
2026-05-20 03:26:54 -07:00
} catch ( error ) {
console . error ( "Error sending OneSignal notification" , error );
res . status ( 500 ). json ({ error : "Failed to send notification" });
}
});
2026-05-25 05:52:17 -07:00
// MetaAPI Provisioning Route
app . post ( "/api/metaapi/provision" , async ( req , res ) => {
const { platform , login , password , serverName } = req . body ;
// User requested token hardcoded
const token = process . env . METAAPI_TOKEN || "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI1YzYxNWEwNjQ1M2JjYTRhMWFhN2Q0Y2U4NzkzMjg2ZiIsImFjY2Vzc1J1bGVzIjpbeyJpZCI6InRyYWRpbmctYWNjb3VudC1tYW5hZ2VtZW50LWFwaSIsIm1ldGhvZHMiOlsidHJhZGluZy1hY2NvdW50LW1hbmFnZW1lbnQtYXBpOnJlc3Q6cHVibGljOio6KiJdLCJyb2xlcyI6WyJyZWFkZXIiLCJ3cml0ZXIiXSwicmVzb3VyY2VzIjpbIio6JFVTRVJfSUQkOioiXX0seyJpZCI6Im1ldGFhcGktcmVzdC1hcGkiLCJtZXRob2RzIjpbIm1ldGFhcGktYXBpOnJlc3Q6cHVibGljOio6KiJdLCJyb2xlcyI6WyJyZWFkZXIiLCJ3cml0ZXIiXSwicmVzb3VyY2VzIjpbIio6JFVTRVJfSUQkOioiXX0seyJpZCI6Im1ldGFhcGktcnBjLWFwaSIsIm1ldGhvZHMiOlsibWV0YWFwaS1hcGk6d3M6cHVibGljOio6KiJdLCJyb2xlcyI6WyJyZWFkZXIiLCJ3cml0ZXIiXSwicmVzb3VyY2VzIjpbIio6JFVTRVJfSUQkOioiXX0seyJpZCI6Im1ldGFhcGktcmVhbC10aW1lLXN0cmVhbWluZy1hcGkiLCJtZXRob2RzIjpbIm1ldGFhcGktYXBpOndzOnB1YmxpYzoqOioiXSwicm9sZXMiOlsicmVhZGVyIiwid3JpdGVyIl0sInJlc291cmNlcyI6WyIqOiRVU0VSX0lEJDoqIl19LHsiaWQiOiJtZXRhc3RhdHMtYXBpIiwibWV0aG9kcyI6WyJtZXRhc3RhdHMtYXBpOnJlc3Q6cHVibGljOio6KiJdLCJyb2xlcyI6WyJyZWFkZXIiLCJ3cml0ZXIiXSwicmVzb3VyY2VzIjpbIio6JFVTRVJfSUQkOioiXX0seyJpZCI6InJpc2stbWFuYWdlbWVudC1hcGkiLCJtZXRob2RzIjpbInJpc2stbWFuYWdlbWVudC1hcGk6cmVzdDpwdWJsaWM6KjoqIl0sInJvbGVzIjpbInJlYWRlciIsIndyaXRlciJdLCJyZXNvdXJjZXMiOlsiKjokVVNFUl9JRCQ6KiJdfSx7ImlkIjoiY29weWZhY3RvcnktYXBpIiwibWV0aG9kcyI6WyJjb3B5ZmFjdG9yeS1hcGk6cmVzdDpwdWJsaWM6KjoqIl0sInJvbGVzIjpbInJlYWRlciIsIndyaXRlciJdLCJyZXNvdXJjZXMiOlsiKjokVVNFUl9JRCQ6KiJdfSx7ImlkIjoibXQtbWFuYWdlci1hcGkiLCJtZXRob2RzIjpbIm10LW1hbmFnZXItYXBpOnJlc3Q6ZGVhbGluZzoqOioiLCJtdC1tYW5hZ2VyLWFwaTpyZXN0OnB1YmxpYzoqOioiXSwicm9sZXMiOlsicmVhZGVyIiwid3JpdGVyIl0sInJlc291cmNlcyI6WyIqOiRVU0VSX0lEJDoqIl19LHsiaWQiOiJiaWxsaW5nLWFwaSIsIm1ldGhvZHMiOlsiYmlsbGluZy1hcGk6cmVzdDpwdWJsaWM6KjoqIl0sInJvbGVzIjpbInJlYWRlciJdLCJyZXNvdXJjZXMiOlsiKjokVVNFUl9JRCQ6KiJdfV0sImlnbm9yZVJhdGVMaW1pdHMiOmZhbHNlLCJ0b2tlbklkIjoiMjAyMTAyMTMiLCJpbXBlcnNvbmF0ZWQiOmZhbHNlLCJyZWFsVXNlcklkIjoiNWM2MTVhMDY0NTNiY2E0YTFhYTdkNGNlODc5MzI4NmYiLCJpYXQiOjE3NzkzMTI4ODJ9.VklJoE6hwcxOSUIINf-tVqOitXzZSf3AfTf2C5oZtnelZnH5F-a6rs1h9V7yNMdch7GVqaSh1reZMH7ZO1zlXOM-Pr2RGJTxs6_iZna3SLHMavyRbTf1Wh9wUu7z16rlsIenj4EVUE4Fuw_aDMpiGELwhPhOlEQa7GKiqCwOWcxox-eYaVYRltQZBiMPEsFD6Dx69SlCx3Ax9Ky73x-BhAcg5znbVM9A3ZT61TfOXZZDIi7tK1ImtWEhI1ZaellmvnZde45V11kikGbkBPSiu9U4Ag6im8CietZRLfxm2uDqzxdPJ1mgSwZvuPFRasHKwgKimp0gmasmIXu7XhOpTv65pCSg3n2v9BeM-WumMi4nJEtrhdxblrNswP4LrFSbuSDSKq11NUQJRXvtssN1i-Dd0RY-bxCZ-eMUvUbCNoyxr5vNmau2oeLSA3M-VGlN8NCF74qOcJKnEIXM_A5qi1NoSAj7emmIE8Oedj0nnK3tOrjs94Hn4N1EZfkNVTcmFVyUoLW9yQKd9sgfhidLOmZ9RaM0iG6G2WsK39v-2_nsj2NwhSQ_bYcMG9bC9tgBOiX8yVA9Y_X9-Yng2w0wI6Hy1wOJ8UTh6PdGAhki38RLulXS657XR3POnqT0jSAsSsisN9C5_hPE7lpY-NW7v5aBZn8pGNlB2PXNnkB885E" ;
if ( ! platform || ! login || ! password || ! serverName ) {
return res . status ( 400 ). json ({ error : "Missing required MT4/MT5 fields" });
}
try {
// @ts-ignore
const metaApi = new MetaApi ( token );
2026-05-30 11:59:02 -07:00
const accounts = await metaApi . metatraderAccountApi . getAccountsWithInfiniteScrollPagination ();
const existingAccount = accounts . find (( a : any ) => a . login === login && a . server === serverName );
if ( existingAccount ) {
return res . json ({ success : true , accountId : existingAccount.id , state : existingAccount.state });
}
2026-05-25 05:52:17 -07:00
const account = await metaApi . metatraderAccountApi . createAccount ({
name : `QuantScan User ${ login } ` ,
login : login ,
password : password ,
server : serverName ,
platform : platform ,
magic : 1000
});
res . json ({ success : true , accountId : account.id , state : account.state });
} catch ( error : any ) {
console . error ( "MetaAPI Connection Error:" , error );
res . status ( 500 ). json ({ error : error.message || "Failed to connect to MetaAPI" });
}
});
2026-06-13 22:47:55 -07:00
// Proxy for Economic Calendar (using fair economy due to 12Data missing macro economic endpoints)
app . get ( "/api/economic_calendar" , async ( req , res ) => {
try {
const calendarRes = await axios . get ( "https://nfs.faireconomy.media/ff_calendar_thisweek.json" );
return res . json ({ success : true , data : calendarRes.data });
} catch ( e : any ) {
console . error ( "Economic calendar error:" , e . message );
return res . status ( 500 ). json ({ success : false , error : "Failed to fetch calendar data" });
}
});
2026-05-20 03:26:54 -07:00
// Vite middleware for development
if ( process . env . NODE_ENV !== "production" ) {
const vite = await createViteServer ({
2026-05-29 11:22:12 -07:00
server : { middlewareMode : true , hmr : false },
2026-05-20 03:26:54 -07:00
appType : "spa" ,
});
app . use ( vite . middlewares );
} else {
const distPath = path . join ( process . cwd (), 'dist' );
app . use ( express . static ( distPath ));
app . get ( '*' , ( req , res ) => {
res . sendFile ( path . join ( distPath , 'index.html' ));
});
}
app . listen ( PORT , "0.0.0.0" , () => {
console . log ( 'Server running on http://localhost:' + PORT );
});
}
startServer ();