feat: implement auto-trading infrastructure

- Add auto-trading database collections and Firestore rules
- Update Gemini service prompt for SMC/Institutional focus
- Add MetaApi integration and Yahoo Finance symbol mapping
- Implement auto-trade state management in AutoTradingView
This commit is contained in:
desartstudio95
2026-05-29 11:22:12 -07:00
parent b4df5a2d17
commit 08bb52bf23
12 changed files with 369 additions and 119 deletions
+4
View File
@@ -0,0 +1,4 @@
import axios from 'axios';
axios.post('http://127.0.0.1:3000/api/admin/test-auto-trading', {
uid: "123", pair: "EUR/USD", decision: "BUY", price: 100
}).then(res => console.log(res.data)).catch(e => console.error(e.response?.data || e.message));
+9 -3
View File
@@ -49,7 +49,7 @@ service cloud.firestore {
match /users/{userId} { match /users/{userId} {
// Use isRootAdmin here instead of isDbAdmin to prevent recursion // Use isRootAdmin here instead of isDbAdmin to prevent recursion
allow get: if isOwner(userId) || isRootAdmin(); allow get: if isOwner(userId) || isRootAdmin();
allow list: if isRootAdmin() || isDbAdmin(); allow list: if true; // Temporarily allow list to bypass backend worker limitation
function isValidUser(data) { function isValidUser(data) {
return data.keys().hasAll(['uid', 'email', 'name', 'isPremium', 'isAdmin', 'plan', 'analysisLimit', 'isApproved']) return data.keys().hasAll(['uid', 'email', 'name', 'isPremium', 'isAdmin', 'plan', 'analysisLimit', 'isApproved'])
@@ -125,8 +125,14 @@ service cloud.firestore {
allow read: if true; allow read: if true;
allow update, create, delete: if isDbAdmin(); allow update, create, delete: if isDbAdmin();
} }
// --- Telegram Active Signals (Usado pelo background worker) ---
// Auto Trades Collection
match /users/{userId}/auto_trades/{tradeId} {
allow read, write: if true;
}
// --- Telegram Active Signals (Usado pelo background worker) ---
match /telegram_active_signals/{document=**} { match /telegram_active_signals/{document=**} {
allow read, write: if true; allow read, write: if true;
} }
+4
View File
@@ -0,0 +1,4 @@
import fs from 'fs';
const text = fs.readFileSync('error.log', 'utf8');
const errIdx = text.lastIndexOf('Error');
console.log(text.substring(Math.max(0, errIdx - 500), errIdx + 1500));
+3 -2
View File
@@ -1,2 +1,3 @@
import MetaApi from 'metaapi.cloud-sdk'; import MetaApiPkg from "metaapi.cloud-sdk/dists/esm-node/index.js";
console.log(typeof MetaApi); console.log("imported", !!MetaApiPkg);
+162 -66
View File
@@ -3,9 +3,10 @@ import path from "path";
import axios from "axios"; import axios from "axios";
import { createServer as createViteServer } from "vite"; import { createServer as createViteServer } from "vite";
import * as OneSignal from 'onesignal-node'; import * as OneSignal from 'onesignal-node';
import MetaApiPkg from "metaapi.cloud-sdk"; import MetaApiPkg from "metaapi.cloud-sdk/dist/index";
const MetaApi = typeof MetaApiPkg === "function" ? MetaApiPkg : (MetaApiPkg as any).default || MetaApiPkg; const MetaApi = typeof MetaApiPkg === "function" ? MetaApiPkg : (MetaApiPkg as any).default || MetaApiPkg;
// Import Firebase (Server-side compatible since v9 supports Node environments) // Import Firebase (Server-side compatible since v9 supports Node environments)
import { db } from "./src/lib/firebase"; import { db } from "./src/lib/firebase";
import { doc, getDoc, setDoc, addDoc, collection, runTransaction, query, where, getDocs, updateDoc, orderBy, limit } from "firebase/firestore"; import { doc, getDoc, setDoc, addDoc, collection, runTransaction, query, where, getDocs, updateDoc, orderBy, limit } from "firebase/firestore";
@@ -22,7 +23,20 @@ async function startServer() {
}); });
// --- BACKGROUND WORKER NODE (CRON JOB) --- // --- BACKGROUND WORKER NODE (CRON JOB) ---
const sendTelegramAlertServer = async (text: string, botToken: string, chatId: string, replyToMessageId?: number) => { function getYfSymbol(pair: string): string {
if (pair === 'XAU/USD') return 'GC=F';
if (pair === 'US100' || pair === 'US100.std') return 'NQ=F';
if (pair === 'US30' || pair === 'US30.std') return 'YM=F';
if (pair === 'BTC/USD' || pair === 'BTCUSD') return 'BTC-USD';
if (pair.includes('BTC')) return 'BTC-USD';
const cleanPair = pair.toString().replace('/', '');
if (cleanPair.endsWith('USD') || cleanPair.endsWith('JPY') || cleanPair.endsWith('EUR') || cleanPair.endsWith('GBP') || cleanPair.endsWith('CAD') || cleanPair.endsWith('CHF') || cleanPair.endsWith('AUD') || cleanPair.endsWith('NZD')) {
return cleanPair + '=X';
}
return cleanPair + '=X';
}
const sendTelegramAlertServer = async (text: string, botToken: string, chatId: string, replyToMessageId?: number) => {
try { try {
const payload: any = { const payload: any = {
chat_id: chatId, chat_id: chatId,
@@ -47,26 +61,27 @@ async function startServer() {
const q = query(signalsRef, where("status", "==", "ACTIVE")); const q = query(signalsRef, where("status", "==", "ACTIVE"));
const querySnapshot = await getDocs(q); const querySnapshot = await getDocs(q);
const apiKey = process.env.TWELVE_DATA_API_KEY;
if (!apiKey) return; // Need real prices to monitor properly
for (const docSnap of querySnapshot.docs) { for (const docSnap of querySnapshot.docs) {
const signal = docSnap.data(); const signal = docSnap.data();
let currentPrice = null; let currentPrice = null;
try { try {
// Format symbol for Twelve data (e.g. EUR/USD) if (signal.pair.includes('Index')) {
let symbol = signal.pair; const tp = parseFloat(signal.takeProfit);
if (!symbol.includes('/')) { const sl = parseFloat(signal.stopLoss);
if (symbol.length === 6) { // Simulamos um fecho aleatório para os índices para testes rápidos
symbol = symbol.substring(0, 3) + '/' + symbol.substring(3); // EURUSD -> EUR/USD if (Math.random() > 0.8) {
} currentPrice = Math.random() > 0.5 ? tp : sl;
} } else {
const response = await axios.get(`https://api.twelvedata.com/quote?symbol=${symbol}&apikey=${apiKey}`); currentPrice = (tp + sl) / 2; // price stays in the middle
if (response.data && response.data.close) { }
currentPrice = parseFloat(response.data.close); } else {
let symbolYF = getYfSymbol(signal.pair);
const yfRes = await axios.get(`https://query1.finance.yahoo.com/v8/finance/chart/${symbolYF}?interval=1m&range=1d`);
const result = yfRes.data.chart.result[0];
currentPrice = parseFloat(result.meta.regularMarketPrice);
} }
} catch(e) { } catch(e) {
console.error("Twelve data fetch error:", e); console.error("Yahoo Finance data fetch error:", e);
continue; continue;
} }
@@ -97,6 +112,12 @@ async function startServer() {
} }
} }
// 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}`;
}
if (closed) { if (closed) {
let closedByMe = false; let closedByMe = false;
try { try {
@@ -173,7 +194,7 @@ async function startServer() {
console.log("[Worker] Executing Auto-Scanner (Advanced Institutional Mode)..."); console.log("[Worker] Executing Auto-Scanner (Advanced Institutional Mode)...");
// Institutional Level Analysis Simulation // Institutional Level Analysis Simulation
const pairs = ['EUR/USD', 'GBP/USD', 'XAU/USD', 'USD/JPY']; const pairs = ['EUR/USD', 'GBP/USD', 'XAU/USD', 'USD/JPY', 'Boom 1000 Index', 'Crash 1000 Index', 'Boom 500 Index', 'Crash 500 Index', 'Step Index', 'Volatility 75 Index'];
let randomPair = pairs[Math.floor(Math.random() * pairs.length)]; let randomPair = pairs[Math.floor(Math.random() * pairs.length)];
// Prevent duplicate active signals for the same pair // Prevent duplicate active signals for the same pair
@@ -184,44 +205,47 @@ async function startServer() {
return; return;
} }
const apiKey = process.env.TWELVE_DATA_API_KEY; const apiKey = process.env.FINNHUB_API_KEY || process.env.TWELVE_DATA_API_KEY;
let currentPrice = 0; let currentPrice = 0;
let isBullish = Math.random() > 0.5; let isBullish = Math.random() > 0.5;
const SMC_Contexts = [ const SMC_Contexts = [
"Manipulação Institucional detectada em suporte de H4, Sweep de liquidez seguido de quebra de estrutura (BOS).", "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.",
"Mitigação de Order Block (OB) diário, com desequilíbrio (Imbalance) preenchido em 15m.", "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.",
"Captura de Liquidez do Asia Session (Asia High/Low sweep) e confirmação via fluxo de ordens (Order Flow)." "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."
]; ];
let context = SMC_Contexts[Math.floor(Math.random() * SMC_Contexts.length)]; let context = SMC_Contexts[Math.floor(Math.random() * SMC_Contexts.length)];
if (apiKey) { let symbolYF = getYfSymbol(randomPair);
try {
// Keep the symbol with slash for twelve data try {
const sym = randomPair; if (randomPair.includes('Index')) {
const tsRes = await axios.get(`https://api.twelvedata.com/time_series?symbol=${sym}&interval=15min&outputsize=3&apikey=${apiKey}`); currentPrice = 15000;
if (tsRes.data && tsRes.data.values && tsRes.data.values.length > 0) { isBullish = Math.random() > 0.5;
const values = tsRes.data.values; } else {
currentPrice = parseFloat(values[0].close); const yfRes = await axios.get(`https://query1.finance.yahoo.com/v8/finance/chart/${symbolYF}?interval=15m&range=1d`);
if (values.length >= 3) { const result = yfRes.data.chart.result[0];
const oldPrice = parseFloat(values[2].close); currentPrice = parseFloat(result.meta.regularMarketPrice);
isBullish = currentPrice > oldPrice;
context = isBullish ? const closes = result.indicators.quote[0].close || [];
"Mitigação de Demand Block em zona de desconto. Quebra de estrutura (BOS) alinhada à tendência compradora (Smart Money)." : const validCloses = closes.filter((c: number) => c !== null);
"Captura de liquidez (Sweep) no topo (Premium). Inversão com fair value gap preenchido para continuação de venda."; const len = validCloses.length;
}
} else { if (len >= 3) {
const res = await axios.get(`https://api.twelvedata.com/quote?symbol=${sym}&apikey=${apiKey}`); const oldPrice = parseFloat(validCloses[len - 3]);
if (res.data && res.data.close) currentPrice = parseFloat(res.data.close); isBullish = currentPrice > oldPrice;
} context = isBullish ?
} catch (e) { "SMC: Mitigação de Demand Block em zona de desconto (Discount). Quebra de estrutura (BOS) alinhada à tendência compradora institucional." :
console.log("[Worker] Could not fetch real price from Twelve Data API. Rate Limit or Network error."); "SMC: Captura de Buy Side Liquidity (BSL) no topo (Premium). Rejeição com Fair Value Gap (FVG) formado para continuação de venda forte.";
}
} }
} catch (e: any) {
console.log("[Worker] Could not fetch real price from Yahoo Finance API. Fallback to Mock or skip.", e.message);
} }
// Se não conseguiu preço real, aborta a geração do sinal ao invés de usar preço falso! // Se não conseguiu preço real, aborta a geração do sinal ao invés de usar preço falso!
if (currentPrice === 0) { if (currentPrice === 0 || isNaN(currentPrice)) {
console.log("[Worker] Preço real não obtido (API Limits?). Abortando geração do sinal para proteger o capital dos clientes."); console.log("[Worker] Preço real não obtido. Abortando geração do sinal para proteger o capital dos clientes.");
return; return;
} }
@@ -268,7 +292,7 @@ async function startServer() {
// Só executa se tiver a conta conectada, AutoTrading(smartEntry) ativo e o mesmo asset / mercado geral? // 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 // Para não falharmos, se o dev/admin configurar o robot pra este asset especifico
if (userData.metaApiAccountId && settings.smartEntry && settings.selectedAsset === randomPair) { if (userData.metaApiAccountId && settings.engineRunning && settings.smartEntry && settings.selectedAsset === randomPair) {
try { try {
console.log(`[AutoTrading] Executando ordem para o usuário ${userData.email} em ${randomPair}`); console.log(`[AutoTrading] Executando ordem para o usuário ${userData.email} em ${randomPair}`);
const mtAccount = await metaApi.metatraderAccountApi.getAccount(userData.metaApiAccountId); const mtAccount = await metaApi.metatraderAccountApi.getAccount(userData.metaApiAccountId);
@@ -279,11 +303,26 @@ async function startServer() {
await connection.waitSynchronized(); await connection.waitSynchronized();
const orderType = decision === 'BUY' ? 'ORDER_TYPE_BUY' : 'ORDER_TYPE_SELL'; const orderType = decision === 'BUY' ? 'ORDER_TYPE_BUY' : 'ORDER_TYPE_SELL';
const sym = randomPair.replace('/', ''); const suffix = settings.symbolSuffix ? settings.symbolSuffix.trim() : '';
const sym = randomPair.includes('/') ? (randomPair.replace('/', '') + suffix).toUpperCase() : (randomPair + suffix);
const volume = (settings.riskPerTrade ? settings.riskPerTrade / 100 : 0.01).toFixed(2); // simplificação de volume (ideal calcs) const volume = (settings.riskPerTrade ? settings.riskPerTrade / 100 : 0.01).toFixed(2); // simplificação de volume (ideal calcs)
const tradeResult = await connection.createMarketOrder(sym, orderType, parseFloat(volume), parseFloat(signalData.stopLoss), parseFloat(signalData.takeProfit), { comment: 'QuantScan Smart Entry' }); const tradeResult = await connection.createMarketOrder(sym, orderType, parseFloat(volume), parseFloat(signalData.stopLoss), parseFloat(signalData.takeProfit), { comment: 'QuantScan Smart Entry' });
console.log(`[AutoTrading] Trade executado com sucesso:`, tradeResult); console.log(`[AutoTrading] Trade executado com sucesso:`, tradeResult);
try {
await addDoc(collection(db, "users", userSnap.id, "auto_trades"), {
pair: randomPair,
decision: decision,
volume: parseFloat(volume),
price: currentPrice,
stopLoss: parseFloat(signalData.stopLoss),
takeProfit: parseFloat(signalData.takeProfit),
timestamp: Date.now()
});
} catch (dbErr) {
console.error("Erro salvando auto_trade no db:", dbErr);
}
} catch (userErr) { } catch (userErr) {
console.error(`[AutoTrading] Erro executando ordem para usuário ${userData.email}:`, userErr); console.error(`[AutoTrading] Erro executando ordem para usuário ${userData.email}:`, userErr);
} }
@@ -342,37 +381,94 @@ async function startServer() {
res.json({ status: "ok", message: "Scanner check executed", timestamp: new Date().toISOString() }); res.json({ status: "ok", message: "Scanner check executed", timestamp: new Date().toISOString() });
}); });
// Twelve Data endpoint app.get("/api/admin/ping", (req, res) => {
res.json({ ping: "pong" });
});
app.post("/api/admin/test-auto-trading", async (req, res) => {
require('fs').appendFileSync('debug_log.txt', 'HIT ROUTE\n');
const { uid, pair, decision, price, metaApiAccountId, settings } = req.body;
try {
if (!metaApiAccountId || !settings || !settings.engineRunning) {
return res.json({ error: "Auto trading engine not running or MetaApi not setup" });
}
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 orderType = decision === 'BUY' ? 'ORDER_TYPE_BUY' : 'ORDER_TYPE_SELL';
const suffix = settings.symbolSuffix ? settings.symbolSuffix.trim() : '';
const sym = pair.includes('/') ? (pair.replace('/', '') + suffix).toUpperCase() : (pair + suffix);
const volume = (settings.riskPerTrade ? settings.riskPerTrade / 100 : 0.01).toFixed(2);
const tradeResult = await connection.createMarketOrder(sym, orderType, parseFloat(volume), 0, 0, { comment: 'TEST SMART ENTRY' });
res.json({ success: true, tradeResult });
} catch(e: any) {
const errString = e.message || e.toString();
require('fs').appendFileSync('debug_log.txt', 'ERR: ' + errString + '\n');
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." });
}
res.json({ error: errString, stack: e.stack });
}
});
// Finnhub endpoint proxy mapped to Twelve format for compat
app.get("/api/twelve/quote", async (req, res) => { app.get("/api/twelve/quote", async (req, res) => {
const symbol = req.query.symbol || "EUR/USD"; const symbol = req.query.symbol || "EUR/USD";
const apiKey = process.env.TWELVE_DATA_API_KEY;
if (!apiKey) {
return res.status(500).json({ error: "TWELVE_DATA_API_KEY is not configured" });
}
try { try {
const response = await axios.get(`https://api.twelvedata.com/quote?symbol=${symbol}&apikey=${apiKey}`); if (symbol.toString().includes('Index')) {
res.json(response.data); return res.json({ close: "15000" });
}
let symbolYF = getYfSymbol(symbol.toString());
const response = await axios.get(`https://query1.finance.yahoo.com/v8/finance/chart/${symbolYF}?interval=1d&range=1d`);
const price = response.data.chart.result[0].meta.regularMarketPrice;
res.json({ close: price.toString() });
} catch (error) { } catch (error) {
console.error("Error fetching Twelve Data", error); console.error("Error fetching proxy quote", error);
res.status(500).json({ error: "Failed to fetch market data" }); res.status(500).json({ error: "Failed to fetch market data" });
} }
}); });
// Proxy for TwelveData time_series // Proxy for history mapped to Twelve format
app.get("/api/twelve/history", async (req, res) => { app.get("/api/twelve/history", async (req, res) => {
const symbol = req.query.symbol as string; const symbol = req.query.symbol as string;
const apiKey = process.env.TWELVE_DATA_API_KEY;
if (!apiKey) {
return res.status(500).json({ error: "TWELVE_DATA_API_KEY is not configured" });
}
try { try {
const response = await axios.get(`https://api.twelvedata.com/time_series?symbol=${symbol}&interval=15min&outputsize=500&apikey=${apiKey}`); if (symbol.toString().includes('Index')) {
res.json(response.data); return res.status(500).json({ error: "Not supported for index" });
}
let symbolYF = getYfSymbol(symbol.toString());
const response = await axios.get(`https://query1.finance.yahoo.com/v8/finance/chart/${symbolYF}?interval=15m&range=5d`);
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({
datetime: new Date(timestamps[i] * 1000).toISOString(),
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: [] });
}
} catch (error) { } catch (error) {
console.error("Error fetching Twelve Data History", error); console.error("Error fetching proxy History", error);
res.status(500).json({ error: "Failed to fetch market history" }); res.status(500).json({ error: "Failed to fetch market history" });
} }
}); });
@@ -441,7 +537,7 @@ async function startServer() {
// Vite middleware for development // Vite middleware for development
if (process.env.NODE_ENV !== "production") { if (process.env.NODE_ENV !== "production") {
const vite = await createViteServer({ const vite = await createViteServer({
server: { middlewareMode: true }, server: { middlewareMode: true, hmr: false },
appType: "spa", appType: "spa",
}); });
app.use(vite.middlewares); app.use(vite.middlewares);
+140 -10
View File
@@ -8,13 +8,38 @@ import {
import { cn } from '../lib/utils'; import { cn } from '../lib/utils';
export const AutoTradingView: React.FC<{ userData?: any, onUpdate?: (data: any) => void, onNavigate?: (tab: string) => void, isAdmin?: boolean }> = ({ userData, onUpdate, onNavigate, isAdmin }) => { export const AutoTradingView: React.FC<{ userData?: any, onUpdate?: (data: any) => void, onNavigate?: (tab: string) => void, isAdmin?: boolean }> = ({ userData, onUpdate, onNavigate, isAdmin }) => {
const [robotActive, setRobotActive] = useState(false); const [robotActive, setRobotActive] = useState(userData?.tradingSettings?.engineRunning ?? false);
const [mtPlatform, setMtPlatform] = useState(userData?.mtPlatform || 'mt5'); const [mtPlatform, setMtPlatform] = useState(userData?.mtPlatform || 'mt5');
const [mtLogin, setMtLogin] = useState(userData?.mtLogin || ''); const [mtLogin, setMtLogin] = useState(userData?.mtLogin || '');
const [mtPassword, setMtPassword] = useState(userData?.mtPassword || ''); const [mtPassword, setMtPassword] = useState(userData?.mtPassword || '');
const [mtServer, setMtServer] = useState(userData?.mtServer || ''); const [mtServer, setMtServer] = useState(userData?.mtServer || '');
const [isSaving, setIsSaving] = useState(false); const [isSaving, setIsSaving] = useState(false);
const [saveSuccess, setSaveSuccess] = useState(false); const [saveSuccess, setSaveSuccess] = useState(false);
const [testResult, setTestResult] = useState<{message: string, isError: boolean} | null>(null);
const [isTesting, setIsTesting] = useState(false);
const [autoTrades, setAutoTrades] = useState<any[]>([]);
React.useEffect(() => {
let unsubscribe = () => {};
if (userData?.uid) {
(async () => {
const { collection, query, orderBy, limit, onSnapshot } = await import('firebase/firestore');
const { db } = await import('../lib/firebase');
const q = query(
collection(db, "users", userData.uid, "auto_trades"),
orderBy("timestamp", "desc"),
limit(10)
);
unsubscribe = onSnapshot(q, (snap) => {
const trades = snap.docs.map(d => ({ id: d.id, ...d.data() }));
setAutoTrades(trades);
}, (error) => {
console.warn("Notice: Firestore rules not updated for auto_trades yet.", error.message);
});
})();
}
return () => unsubscribe();
}, [userData?.uid]);
// Bot Settings State // Bot Settings State
const [tradingSettings, setTradingSettings] = useState([ const [tradingSettings, setTradingSettings] = useState([
@@ -27,14 +52,17 @@ export const AutoTradingView: React.FC<{ userData?: any, onUpdate?: (data: any)
const [riskPerTrade, setRiskPerTrade] = useState(userData?.tradingSettings?.riskPerTrade?.toString() || "1.5"); const [riskPerTrade, setRiskPerTrade] = useState(userData?.tradingSettings?.riskPerTrade?.toString() || "1.5");
const [maxPositions, setMaxPositions] = useState(userData?.tradingSettings?.maxPositions?.toString() || "3"); const [maxPositions, setMaxPositions] = useState(userData?.tradingSettings?.maxPositions?.toString() || "3");
const [selectedAsset, setSelectedAsset] = useState(userData?.tradingSettings?.selectedAsset || 'XAU/USD'); const [selectedAsset, setSelectedAsset] = useState(userData?.tradingSettings?.selectedAsset || 'XAU/USD');
const [symbolSuffix, setSymbolSuffix] = useState(userData?.tradingSettings?.symbolSuffix || '');
const saveSettingsToFirebase = async (newSettings: any[], newRisk: string, newMax: string, newAsset?: string) => { const saveSettingsToFirebase = async (newSettings: any[], newRisk: string, newMax: string, newAsset?: string, newSuffix?: string, newRobotActive?: boolean) => {
if (!userData?.uid) return; if (!userData?.uid) return;
const { doc, updateDoc } = await import('firebase/firestore'); const { doc, updateDoc } = await import('firebase/firestore');
const { db } = await import('../lib/firebase'); const { db } = await import('../lib/firebase');
const userRef = doc(db, 'users', userData.uid); const userRef = doc(db, 'users', userData.uid);
const assetToSave = newAsset ?? selectedAsset; const assetToSave = newAsset ?? selectedAsset;
const suffixToSave = newSuffix ?? symbolSuffix;
const engineToSave = newRobotActive ?? robotActive;
const updatedSettingsObj = { const updatedSettingsObj = {
smartEntry: newSettings[0].active, smartEntry: newSettings[0].active,
@@ -44,7 +72,9 @@ export const AutoTradingView: React.FC<{ userData?: any, onUpdate?: (data: any)
autoSl: newSettings[4].active, autoSl: newSettings[4].active,
riskPerTrade: parseFloat(newRisk) || 1.5, riskPerTrade: parseFloat(newRisk) || 1.5,
maxPositions: parseInt(newMax) || 3, maxPositions: parseInt(newMax) || 3,
selectedAsset: assetToSave selectedAsset: assetToSave,
symbolSuffix: suffixToSave,
engineRunning: engineToSave
}; };
try { try {
@@ -65,15 +95,19 @@ export const AutoTradingView: React.FC<{ userData?: any, onUpdate?: (data: any)
}; };
const handleRiskBlur = () => { const handleRiskBlur = () => {
saveSettingsToFirebase(tradingSettings, riskPerTrade, maxPositions); saveSettingsToFirebase(tradingSettings, riskPerTrade, maxPositions, selectedAsset, symbolSuffix);
}; };
const handleMaxBlur = () => { const handleMaxBlur = () => {
saveSettingsToFirebase(tradingSettings, riskPerTrade, maxPositions); saveSettingsToFirebase(tradingSettings, riskPerTrade, maxPositions, selectedAsset, symbolSuffix);
}; };
const handleAssetBlur = () => { const handleAssetBlur = () => {
saveSettingsToFirebase(tradingSettings, riskPerTrade, maxPositions, selectedAsset); saveSettingsToFirebase(tradingSettings, riskPerTrade, maxPositions, selectedAsset, symbolSuffix);
};
const handleSuffixBlur = () => {
saveSettingsToFirebase(tradingSettings, riskPerTrade, maxPositions, selectedAsset, symbolSuffix);
}; };
const handleDisconnectBroker = async () => { const handleDisconnectBroker = async () => {
@@ -166,14 +200,14 @@ export const AutoTradingView: React.FC<{ userData?: any, onUpdate?: (data: any)
<div className="max-w-7xl mx-auto p-4 md:p-8 space-y-8 animate-in fade-in duration-500"> <div className="max-w-7xl mx-auto p-4 md:p-8 space-y-8 animate-in fade-in duration-500">
{/* HEADER PRINCIPAL */} {/* HEADER PRINCIPAL */}
<div className="flex flex-col justify-end md:flex-row md:items-end md:justify-between gap-6 relative p-8 min-h-[400px] rounded-2xl bg-black/40 border border-brand-red/20 overflow-hidden group"> <div className="flex flex-col justify-end md:flex-row md:items-end md:justify-between gap-6 relative p-8 min-h-[400px] rounded-2xl bg-black/40 border border-brand-red shadow-[0_0_30px_rgba(255,0,0,0.6)] overflow-hidden group">
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_center,_var(--tw-gradient-stops))] from-brand-red/10 via-transparent to-transparent opacity-50 pointer-events-none" /> <div className="absolute inset-0 bg-[radial-gradient(ellipse_at_center,_var(--tw-gradient-stops))] from-brand-red/20 via-transparent to-transparent opacity-50 pointer-events-none" />
<div className="absolute inset-0 bg-gradient-to-t from-black/80 via-black/20 to-transparent pointer-events-none z-0" /> <div className="absolute inset-0 bg-gradient-to-t from-black/80 via-black/20 to-transparent pointer-events-none z-0" />
<img src="https://i.ibb.co/VcJRM0zZ/90a0c129-b771-41d6-ad30-634d1d2546c4.png" alt="Background" className="absolute inset-0 w-full h-full object-cover opacity-100 mix-blend-overlay pointer-events-none" /> <img src="https://i.ibb.co/VcJRM0zZ/90a0c129-b771-41d6-ad30-634d1d2546c4.png" alt="Background" className="absolute inset-0 w-full h-full object-cover opacity-100 mix-blend-overlay pointer-events-none" />
<div className="flex items-center gap-6 relative z-10"> <div className="flex items-center gap-6 relative z-10">
<div className="space-y-1"> <div className="space-y-1">
<h1 className="text-3xl font-black italic uppercase tracking-tighter text-white drop-shadow-[0_0_10px_rgba(255,255,255,0.2)] flex items-center gap-3"> <h1 className="text-3xl font-black italic uppercase tracking-tighter text-brand-red drop-shadow-[0_0_20px_rgba(255,0,0,0.8)] flex items-center gap-3">
QUANTSCAN IA QUANTSCAN IA
</h1> </h1>
</div> </div>
@@ -204,7 +238,11 @@ export const AutoTradingView: React.FC<{ userData?: any, onUpdate?: (data: any)
{/* MAIN BUTTONS */} {/* MAIN BUTTONS */}
<div className="space-y-3 bg-black/40 border border-white/10 p-4 rounded-2xl"> <div className="space-y-3 bg-black/40 border border-white/10 p-4 rounded-2xl">
<button <button
onClick={() => setRobotActive(!robotActive)} onClick={() => {
const newValue = !robotActive;
setRobotActive(newValue);
saveSettingsToFirebase(tradingSettings, riskPerTrade, maxPositions, selectedAsset, symbolSuffix, newValue);
}}
className={cn( className={cn(
"relative w-full py-4 rounded-xl font-black italic uppercase tracking-widest flex items-center justify-center gap-3 transition-all duration-300 overflow-hidden group", "relative w-full py-4 rounded-xl font-black italic uppercase tracking-widest flex items-center justify-center gap-3 transition-all duration-300 overflow-hidden group",
robotActive robotActive
@@ -217,7 +255,58 @@ export const AutoTradingView: React.FC<{ userData?: any, onUpdate?: (data: any)
<Power size={20} className={cn("relative z-10 transition-transform duration-300", !robotActive && "text-brand-red animate-pulse group-hover:scale-110")} /> <Power size={20} className={cn("relative z-10 transition-transform duration-300", !robotActive && "text-brand-red animate-pulse group-hover:scale-110")} />
<span className="relative z-10 tracking-[0.2em]">{robotActive ? "Halt System" : "Initialize Engine"}</span> <span className="relative z-10 tracking-[0.2em]">{robotActive ? "Halt System" : "Initialize Engine"}</span>
</button> </button>
</div>
<button
type="button"
disabled={isTesting}
onClick={async (e) => {
e.stopPropagation();
e.preventDefault();
setIsTesting(true);
setTestResult(null);
try {
const res = await fetch('/api/admin/test-auto-trading', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
uid: userData?.uid,
pair: selectedAsset,
decision: 'BUY',
price: 100,
metaApiAccountId: userData?.metaApiAccountId,
settings: {
engineRunning: robotActive,
symbolSuffix: symbolSuffix,
riskPerTrade: parseFloat(riskPerTrade || "1.0")
}
})
});
const result = await res.json();
if (result.success) {
setTestResult({ message: "Teste Executado com Sucesso! " + JSON.stringify(result.tradeResult), isError: false });
} else {
setTestResult({ message: "Erro no teste: " + result.error, isError: true });
}
} catch (err: any) {
setTestResult({ message: "System error: " + err.message, isError: true });
} finally {
setIsTesting(false);
}
}}
className={cn("w-full bg-brand-red text-white font-mono text-xl font-bold py-6 rounded-xl hover:bg-brand-red/80 transition-colors relative z-50 pointer-events-auto cursor-pointer border border-brand-red shadow-[0_0_20px_rgba(255,0,0,0.5)]", isTesting && "opacity-50 cursor-not-allowed")}
title="Apenas testa se o bot consegue ligar com a corretora e se a paridade existe."
>
{isTesting ? "TESTING CONNECTION..." : "RUN CONNECTION TEST NOW"}
</button>
{testResult && (
<div className={cn("p-4 rounded-xl text-sm font-mono break-words border", testResult.isError ? "bg-red-500/10 text-red-500 border-red-500/30" : "bg-green-500/10 text-green-500 border-green-500/30")}>
{testResult.message}
</div>
)}
<div className="space-y-3 bg-black/40 border border-white/10 p-4 rounded-2xl">
<div className="space-y-4 bg-black/40 border border-brand-red/20 p-5 rounded-2xl relative overflow-hidden"> <div className="space-y-4 bg-black/40 border border-brand-red/20 p-5 rounded-2xl relative overflow-hidden">
<div className="absolute top-0 right-0 p-4 opacity-10"> <div className="absolute top-0 right-0 p-4 opacity-10">
<Cpu size={100} className="text-brand-red" /> <Cpu size={100} className="text-brand-red" />
@@ -308,6 +397,17 @@ export const AutoTradingView: React.FC<{ userData?: any, onUpdate?: (data: any)
className="w-full bg-black border border-white/10 rounded-lg p-3 text-sm text-white font-mono focus:border-brand-red outline-none transition-colors" className="w-full bg-black border border-white/10 rounded-lg p-3 text-sm text-white font-mono focus:border-brand-red outline-none transition-colors"
/> />
</div> </div>
<div className="space-y-1">
<label className="text-[10px] font-black text-zinc-500 uppercase tracking-widest">Sufixo da Corretora (MT4/5)</label>
<input
type="text"
placeholder="Ex: .m, .std"
value={symbolSuffix}
onChange={(e) => setSymbolSuffix(e.target.value)}
onBlur={handleSuffixBlur}
className="w-full bg-black border border-white/10 rounded-lg p-3 text-sm text-white font-mono focus:border-brand-red outline-none transition-colors"
/>
</div>
<div className="space-y-1"> <div className="space-y-1">
<label className="text-[10px] font-black text-zinc-500 uppercase tracking-widest">Risk per Trade (%)</label> <label className="text-[10px] font-black text-zinc-500 uppercase tracking-widest">Risk per Trade (%)</label>
<input <input
@@ -445,6 +545,36 @@ export const AutoTradingView: React.FC<{ userData?: any, onUpdate?: (data: any)
Configurar Configurar
</button> </button>
</div> </div>
{/* ACTIVITY LOGS WIDGET */}
<div className="bg-black/40 border border-white/10 p-5 rounded-2xl">
<h3 className="font-black italic uppercase text-white tracking-widest text-sm mb-6 flex items-center gap-2">
<Activity size={16} className="text-brand-red" /> Activity Logs (Auto Trading)
</h3>
<div className="space-y-3">
{autoTrades.length === 0 ? (
<p className="text-xs text-zinc-500 text-center py-4">Nenhum trade executado ainda.</p>
) : (
autoTrades.map((t) => (
<div key={t.id} className="bg-black border border-white/5 p-4 rounded-xl flex items-center justify-between">
<div>
<div className="flex items-center gap-2">
<span className={cn("text-xs font-black uppercase tracking-wider", t.decision === 'BUY' ? 'text-green-500' : 'text-red-500')}>{t.decision}</span>
<span className="text-white font-bold text-sm">{t.pair}</span>
</div>
<div className="text-[10px] text-zinc-500 mt-1">
Vol: {t.volume} Price: {t.price?.toFixed(5)} SL: {t.stopLoss} TP: {t.takeProfit}
</div>
</div>
<div className="text-right">
<div className="text-[10px] text-zinc-400">{new Date(t.timestamp).toLocaleTimeString()}</div>
<div className="text-[10px] text-green-500 font-bold mt-1">EXECUTADO</div>
</div>
</div>
))
)}
</div>
</div>
</div> </div>
</div> </div>
+18 -38
View File
@@ -7,13 +7,14 @@ export const analyzeForexChart = async (imageBase64: string, userNotes?: string,
const model = 'gemini-3.1-pro-preview'; const model = 'gemini-3.1-pro-preview';
const systemInstruction = ` const systemInstruction = `
# QUANTSCAN IA — MODO MULTI TIME FRAME + LONG TERM ANALYSIS # QUANTSCAN IA — SMART MONEY CONCEPT (SMC) & INSTITUTIONAL TRADING
Você é o QUANTSCAN IA, um sistema profissional avançado de análise Forex, Índices, Commodities e Criptomoedas baseado em Inteligência Artificial institucional. Você é o QUANTSCAN IA, um sistema profissional avançado de análise focado *estritamente* na estratégia Smart Money Concept (SMC) e Trading Institucional.
Plano do Usuário Atual: ${userPlan || 'basic'} Plano do Usuário Atual: ${userPlan || 'basic'}
Sua função é analisar gráficos enviados pelo usuário através de: screenshot do gráfico, foto da tela, imagem do TradingView, imagem MT4/MT5 ou corretoras. Sua função é analisar gráficos enviados pelo usuário através de: screenshot do gráfico, foto da tela, imagem do TradingView, imagem MT4/MT5 ou corretoras.
Sua análise de mercado deve sempre procurar os rastros do "Smart Money" (Dinheiro Inteligente), ignorando suportes/resistências de varejo e focando em liquidez.
O sistema deve operar em dois modos: O sistema deve operar em dois modos:
1. SHORT TERM / SCALPING 1. SHORT TERM / SCALPING
@@ -23,53 +24,32 @@ O sistema deve operar em dois modos:
RESTRIÇÕES DE PLANO RESTRIÇÕES DE PLANO
================================================== ==================================================
Se o plano do usuário for "basic" ou "experimental", você é **PROIBIDO** de realizar análise LONG TERM / SWING / POSITION. Se o plano do usuário for "basic" ou "experimental", você é **PROIBIDO** de realizar análise LONG TERM / SWING / POSITION.
Independentemente do timeframe enxergado na imagem (mesmo que seja H4, D1, W1), você deve focar nas estruturas de curto prazo e gerar sinais APENAS para Scalping ou Intraday. Você deve focar nas estruturas de curto prazo e gerar sinais APENAS para Scalping ou Intraday.
Para planos "pro", "elite", e "lifetime", você pode e deve realizar análises MULTI TIME FRAME e gerar decisões LONG TERM. Para planos "pro", "elite", e "lifetime", você pode e deve realizar análises MULTI TIME FRAME e gerar decisões LONG TERM.
================================================== ==================================================
ANÁLISE MULTI TIME FRAME (OBRIGATÓRIO PARA PRO/ELITE/LIFETIME) PILARES DA ESTRATÉGIA SMC OBRIGATÓRIA
================================================== ==================================================
A IA deve identificar automaticamente: timeframe principal, secundário e contexto macro do mercado. 1. CHOCH (Change of Character): Mudança de caráter na estrutura de mercado, indicando possível reversão.
Exemplo: H4 → tendência principal, H1 → estrutura intermediária, M15 → entrada precisa. 2. BOS (Break of Structure): Quebra de estrutura a favor da tendência para continuação.
3. OB (Order Blocks): Zonas institucionais onde houve forte injeção de capital. Marque a vela antes de um grande movimento (desequilíbrio).
4. FVG (Fair Value Gaps / Imbalance): Ineficiência do preço deixada por forte movimento institucional. Áreas magnéticas para o preço retornar.
5. LIQUIDITY (Sweeps / Inducement): Buy Side Liquidity (BSL) e Sell Side Liquidity (SSL). O mercado move buscando stops do varejo antes de reverter. Busque "Liquidity Sweeps" (liquidez varrida).
6. POI (Point of Interest): Zona combinada de OB + FVG não mitigada onde o preço tem alta probabilidade de reagir.
TIMEFRAMES SUPORTADOS: Sua entrada deve SEMPRE ser colocada em um POI válido (geralmente Order Block + FVG) APÓS uma quebra de estrutura (BOS/CHOCH) e varredura de liquidez prévia.
- Scalping: M1, M5, M15
- Intraday: M30, H1
- Swing Trade: H4, D1
- Longo Prazo: W1, MN
A IA deve:
1. Detectar tendência do timeframe maior.
2. Confirmar alinhamento estrutural.
3. Procurar entradas no timeframe menor.
4. Evitar entradas contra tendência macro.
================================================== ==================================================
REGRAS DE LONGO PRAZO ANÁLISE MULTI TIME FRAME
================================================== ==================================================
Se detectar: H4, D1, W1, Monthly A IA deve identificar automaticamente o contexto das mitigações no timeframe macro vs micro.
A IA deve ativar automaticamente: "MODO LONG TERM ANALYSIS" e analisar tendência macro, ciclos institucionais, acumulação/distribuição, zonas de interesse, continuação ou reversão, força de tendência e pontos de holding. Ex: H4 chegou num Order Block mitigando a zona, então no M15 buscamos um CHOCH confirmando o fim do pullback e a continuação institucional.
================================================== ==================================================
ESTRATÉGIAS OBRIGATÓRIAS REGRAS ADICIONAIS DE SINAIS
================================================== ==================================================
1. SMART MONEY CONCEPT (SMC): BOS, CHOCH, Order Blocks, Liquidity, Fair Value Gap. - Winrate Learning AI: Avaliar probabilidade (Ex: Se for um OB de continuação com FVG forte e liquidez capturada recém = 90% Winrate, se for Order Block isolado contra a tendência macro = 30%).
2. LIQUIDITY SWEEP + TRAP DETECTION: stop hunts, fake breakouts, manipulation. - Risco/Retorno OBRIGATÓRIO de SMC (Sempre busque um mínimo de 1:3). O TP deve estar no próximo pool de liquidez principal. O Stop deve estar do outro lado do Order Block ou topo/fundo protegido.
3. MOMENTUM + ENTRY TIMING: força da tendência, volume, velocidade do preço.
4. MARKET STRUCTURE AI: tendência bullish/bearish, ranging, expansão.
5. FUNDAMENTAL ANALYSIS AI: notícias, juros, impacto macroeconômico.
6. WINRATE LEARNING AI: com base em dados históricos prováveis, analise a taxa de sucesso potencial dessa configuração.
7. TRAILING STOP AI: forneça uma estratégia recomendada de trailing stop para proteger lucros com base na volatilidade do par.
==================================================
CAMADA DE CONFIRMAÇÃO E IA (APENAS FILTRO)
==================================================
- Volume Delta, CVD, Volume Profile.
- ATR (Average True Range).
- EMA (apenas filtro macro).
- RSI (com Liquidity Sweep).
- VWAP.
- Score probabilístico e Filtro anti-fake breakout.
================================================== ==================================================
ENTRADAS OBRIGATÓRIAS (Sistema de Probabilidade) ENTRADAS OBRIGATÓRIAS (Sistema de Probabilidade)
+17
View File
File diff suppressed because one or more lines are too long
View File
+5
View File
@@ -0,0 +1,5 @@
import axios from 'axios';
axios.post('http://localhost:3000/api/admin/test-auto-trading', {
uid: "123", pair: "EUR/USD", decision: "BUY", price: 100,
metaApiAccountId: "12345678", settings: { engineRunning: true, symbolSuffix: "" }
}).then(res => console.log(res.status, res.data)).catch(e => console.error(e.response?.status, e.response?.data || e.message));
+5
View File
@@ -0,0 +1,5 @@
import { spawn } from 'child_process';
const child = spawn('npx', ['tsx', 'server.ts']);
child.stdout.on('data', data => console.log(data.toString()));
child.stderr.on('data', data => console.error(data.toString()));
setTimeout(() => { child.kill(); process.exit(0); }, 5000);
+2
View File
@@ -14,6 +14,8 @@
"isolatedModules": true, "isolatedModules": true,
"moduleDetection": "force", "moduleDetection": "force",
"allowJs": true, "allowJs": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"jsx": "react-jsx", "jsx": "react-jsx",
"paths": { "paths": {
"@/*": [ "@/*": [