diff --git a/firestore.rules b/firestore.rules index 6585d1e..153edad 100644 --- a/firestore.rules +++ b/firestore.rules @@ -132,6 +132,11 @@ service cloud.firestore { allow read, write: if true; } + // Global Auto Trades Collection + match /global_auto_trades/{document=**} { + allow read, write: if true; + } + // --- Telegram Active Signals (Usado pelo background worker) --- match /telegram_active_signals/{document=**} { allow read, write: if true; diff --git a/min_test.ts b/min_test.ts deleted file mode 100644 index 15aaecc..0000000 --- a/min_test.ts +++ /dev/null @@ -1,3 +0,0 @@ -import MetaApiPkg from "metaapi.cloud-sdk/dists/esm-node/index.js"; -console.log("imported", !!MetaApiPkg); - diff --git a/package.json b/package.json index 82e816d..7b8fa0c 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "embla-carousel-autoplay": "^8.6.0", "embla-carousel-react": "^8.6.0", "express": "^4.21.2", - "firebase": "^12.12.1", + "firebase": "^10.12.2", "lightweight-charts": "^5.2.0", "lucide-react": "^0.546.0", "metaapi.cloud-sdk": "^29.3.3", @@ -41,7 +41,7 @@ "vite": "^6.2.0" }, "devDependencies": { - "@firebase/rules-unit-testing": "^5.0.0", + "@firebase/rules-unit-testing": "^3.0.4", "@types/express": "^4.17.21", "@types/node": "^22.14.0", "autoprefixer": "^10.4.21", diff --git a/server.ts b/server.ts index 1edb87a..47f1b12 100644 --- a/server.ts +++ b/server.ts @@ -229,9 +229,11 @@ const sendTelegramAlertServer = async (text: string, botToken: string, chatId: s const symD = derivMap[randomPair]; if (symD) { - const getDerivData = () => new Promise((resolve, reject) => { - const WebSocket = require('ws'); - const ws = new WebSocket('wss://ws.binaryws.com/websockets/v3?app_id=1089'); + const getDerivData = () => new Promise(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'); ws.on('open', () => { // Get Candles of 15m format @@ -252,6 +254,9 @@ const sendTelegramAlertServer = async (text: string, botToken: string, chatId: s ws.on('error', reject); setTimeout(() => { ws.close(); reject('Timeout WS'); }, 5000); + } catch (e) { + reject(e); + } }); const candles: any = await getDerivData(); @@ -361,7 +366,7 @@ const sendTelegramAlertServer = async (text: string, botToken: string, chatId: s }); console.log(`[GlobalAI] Trade Master executado com sucesso:`, globalTradeResult); - await addDoc(collection(db, "global_auto_trades"), { + await addDoc(collection(db, "users", "global", "auto_trades"), { pair: randomPair, decision: decision, stake: stake, @@ -391,9 +396,31 @@ const sendTelegramAlertServer = async (text: string, botToken: string, chatId: s // Para não falharmos, se o dev/admin configurar o robot pra este asset especifico if ((userData.metaApiAccountId || userData.mtPlatform === 'deriv_api') && settings.engineRunning && settings.smartEntry && settings.selectedAsset === randomPair) { try { + // 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 + } + } + console.log(`[AutoTrading] Executando ordem para o usuário ${userData.email} em ${randomPair} via ${userData.mtPlatform}`); - const volume = parseFloat((settings.riskPerTrade ? settings.riskPerTrade / 100 : 0.01).toFixed(2)); + 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}`); + let tradeResult = null; if (userData.mtPlatform === 'deriv_api') { @@ -488,7 +515,7 @@ const sendTelegramAlertServer = async (text: string, botToken: string, chatId: s await addDoc(collection(db, "users", userSnap.id, "auto_trades"), { pair: randomPair, decision: decision, - volume: parseFloat(volume), + volume: volume, price: currentPrice, stopLoss: parseFloat(signalData.stopLoss), takeProfit: parseFloat(signalData.takeProfit), @@ -713,7 +740,9 @@ const sendTelegramAlertServer = async (text: string, botToken: string, chatId: s const suffix = settings.symbolSuffix ? settings.symbolSuffix.trim() : ''; const sym = pair.includes('/') ? (pair.replace('/', '') + suffix).toUpperCase() : (pair + suffix); - const volume = parseFloat((settings.riskPerTrade ? settings.riskPerTrade / 100 : 0.01).toFixed(2)); + + let volume = parseFloat((settings.riskPerTrade ? settings.riskPerTrade / 100 : 0.01).toFixed(2)); + if (settings.lotType === 'fixed') volume = parseFloat(settings.fixedLot || 0.01); let tradeResult; if (decision === 'BUY') { diff --git a/src/components/AdminDashboard.tsx b/src/components/AdminDashboard.tsx index 17e3699..e3b2b81 100644 --- a/src/components/AdminDashboard.tsx +++ b/src/components/AdminDashboard.tsx @@ -1,7 +1,7 @@ import React, { useState, useEffect } from 'react'; import { Users, ShieldCheck, Search, Loader2, Settings, ShieldAlert } from 'lucide-react'; import { cn } from '../lib/utils'; -import { collection, onSnapshot, doc, updateDoc, addDoc, deleteDoc, query, orderBy, getDoc, setDoc } from 'firebase/firestore'; +import { collection, onSnapshot, doc, updateDoc, addDoc, deleteDoc, query, orderBy, getDoc, setDoc, limit } from 'firebase/firestore'; import { db, handleFirestoreError, OperationType, auth } from '../lib/firebase'; import { ImageUploader } from './ImageUploader'; import { sendTelegramAlert } from '../services/notifications'; @@ -42,6 +42,7 @@ export const AdminDashboard: React.FC = () => { const [globalDerivToken, setGlobalDerivToken] = useState(''); const [globalTradeStake, setGlobalTradeStake] = useState(1); + const [globalTrades, setGlobalTrades] = useState([]); useEffect(() => { if (autoScannerActive) { @@ -111,10 +112,18 @@ export const AdminDashboard: React.FC = () => { console.warn("Failed to listen to settings:", error); }); + const qGlobal = query(collection(db, 'users', 'global', 'auto_trades'), orderBy('timestamp', 'desc'), limit(10)); + const unsubGlobalTrades = onSnapshot(qGlobal, (snapshot) => { + setGlobalTrades(snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }))); + }, (error) => { + console.error("Global trades fetch error:", error); + }); + return () => { unsub(); unsubTestimonials(); unsubSettings(); + unsubGlobalTrades(); }; }, []); @@ -426,6 +435,31 @@ export const AdminDashboard: React.FC = () => { /> + +
+ Histórico de Trades da IA Global (Master) + {globalTrades.length === 0 ? ( +

Nenhum trade realizado ainda.

+ ) : ( +
+ {globalTrades.map(trade => ( +
+
+ {trade.pair} + + {trade.decision} @ {trade.price} + +
+
+ ${trade.stake} Stake + {new Date(trade.timestamp).toLocaleString()} +
+
+ ))} +
+ )} +
+ diff --git a/src/components/AutoTradingView.tsx b/src/components/AutoTradingView.tsx index 1d163ee..f45e1b3 100644 --- a/src/components/AutoTradingView.tsx +++ b/src/components/AutoTradingView.tsx @@ -55,8 +55,26 @@ export const AutoTradingView: React.FC<{ userData?: any, onUpdate?: (data: any) const [maxPositions, setMaxPositions] = useState(userData?.tradingSettings?.maxPositions?.toString() || "3"); const [selectedAsset, setSelectedAsset] = useState(userData?.tradingSettings?.selectedAsset || 'XAU/USD'); const [symbolSuffix, setSymbolSuffix] = useState(userData?.tradingSettings?.symbolSuffix || ''); + + const [lotType, setLotType] = useState(userData?.tradingSettings?.lotType || 'risk'); + const [fixedLot, setFixedLot] = useState(userData?.tradingSettings?.fixedLot?.toString() || "0.01"); + const [breakEvenEnabled, setBreakEvenEnabled] = useState(userData?.tradingSettings?.breakEvenEnabled ?? true); + const [tradingHoursStart, setTradingHoursStart] = useState(userData?.tradingSettings?.tradingHoursStart || "00:00"); + const [tradingHoursEnd, setTradingHoursEnd] = useState(userData?.tradingSettings?.tradingHoursEnd || "23:59"); - const saveSettingsToFirebase = async (newSettings: any[], newRisk: string, newMax: string, newAsset?: string, newSuffix?: string, newRobotActive?: boolean) => { + const saveSettingsToFirebase = async ( + newSettings: any[], + newRisk: string, + newMax: string, + newAsset?: string, + newSuffix?: string, + newRobotActive?: boolean, + newLotType?: string, + newFixedLot?: string, + newBeEnabled?: boolean, + newHoursStart?: string, + newHoursEnd?: string + ) => { if (!userData?.uid) return; const { doc, updateDoc } = await import('firebase/firestore'); const { db } = await import('../lib/firebase'); @@ -71,12 +89,17 @@ export const AutoTradingView: React.FC<{ userData?: any, onUpdate?: (data: any) copyTrades: newSettings[1].active, newsFilter: newSettings[2].active, autoTp: newSettings[3].active, - autoSl: newSettings[4].active, + autoSl: newSettings[4].active, // used as Trailing Stop toggle riskPerTrade: parseFloat(newRisk) || 1.5, maxPositions: parseInt(newMax) || 3, selectedAsset: assetToSave, symbolSuffix: suffixToSave, - engineRunning: engineToSave + engineRunning: engineToSave, + lotType: newLotType ?? lotType, + fixedLot: parseFloat(newFixedLot ?? fixedLot) || 0.01, + breakEvenEnabled: newBeEnabled ?? breakEvenEnabled, + tradingHoursStart: newHoursStart ?? tradingHoursStart, + tradingHoursEnd: newHoursEnd ?? tradingHoursEnd }; try { @@ -97,19 +120,27 @@ export const AutoTradingView: React.FC<{ userData?: any, onUpdate?: (data: any) }; const handleRiskBlur = () => { - saveSettingsToFirebase(tradingSettings, riskPerTrade, maxPositions, selectedAsset, symbolSuffix); + saveSettingsToFirebase(tradingSettings, riskPerTrade, maxPositions); + }; + + const handleFixedLotBlur = () => { + saveSettingsToFirebase(tradingSettings, riskPerTrade, maxPositions); }; const handleMaxBlur = () => { - saveSettingsToFirebase(tradingSettings, riskPerTrade, maxPositions, selectedAsset, symbolSuffix); + saveSettingsToFirebase(tradingSettings, riskPerTrade, maxPositions); }; const handleAssetBlur = () => { - saveSettingsToFirebase(tradingSettings, riskPerTrade, maxPositions, selectedAsset, symbolSuffix); + saveSettingsToFirebase(tradingSettings, riskPerTrade, maxPositions); }; const handleSuffixBlur = () => { - saveSettingsToFirebase(tradingSettings, riskPerTrade, maxPositions, selectedAsset, symbolSuffix); + saveSettingsToFirebase(tradingSettings, riskPerTrade, maxPositions); + }; + + const handleHoursBlur = () => { + saveSettingsToFirebase(tradingSettings, riskPerTrade, maxPositions); }; const handleDisconnectBroker = async () => { @@ -255,28 +286,58 @@ export const AutoTradingView: React.FC<{ userData?: any, onUpdate?: (data: any) return (
- {/* HEADER PRINCIPAL */} -
-
-
- Background + {/* HEADER PRINCIPAL - CYBERPUNK / HIGH-TECH */} +
+ {/* TECH BACKGROUND */} +
+
+
-
-
-

- QUANTSCAN IA + {/* GLOWING GRID */} +
+ + Background + +
+
+
+
+ Neural Link v4.2 Active +
+

+ QUANTSCAN IA + AUTOTRADING SYNAPSE

-
+
+ + {/* METRICS HEADER */} +
+
+
Core Load
+
{(Math.random() * 20 + 10).toFixed(1)}%
+
+
+
+
Latency
+
{Math.floor(Math.random() * 20 + 5)}ms
+
+
-
-
-
Status Global
-
- {robotActive ? "ONLINE" : "OFFLINE"} +
+
+
+
+
+ +
+ Engine Status + {robotActive ? "ONLINE" : "OFFLINE"} +
+
@@ -365,22 +426,27 @@ export const AutoTradingView: React.FC<{ userData?: any, onUpdate?: (data: any)
)} -
-
-
- +
+
+
+
+
-

- Robôs Conectados +

+ ACTIVE SUBSYSTEMS

-
-
-
-
QuantScan IA PRO
-
Trading Institucional HFR
+
+
+
+
+
QuantScan IA PRO
+
Institutional HFR
+
+
+
+
-
@@ -415,9 +481,11 @@ export const AutoTradingView: React.FC<{ userData?: any, onUpdate?: (data: any) {/* MIDDLE COLUMN: SETTINGS */}
-
-

- Auto Trading Settings +
+
+
+

+ PARAMETERS & PROTOCOLS

@@ -513,6 +581,101 @@ export const AutoTradingView: React.FC<{ userData?: any, onUpdate?: (data: any) />
+ + {/* ADVANCED SETTINGS ROW */} +
+
+ +
+ + +
+
+
+ + {lotType === 'risk' ? ( + setRiskPerTrade(e.target.value)} + onBlur={handleRiskBlur} + step="0.1" + 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" + /> + ) : ( + setFixedLot(e.target.value)} + onBlur={handleFixedLotBlur} + step="0.01" + 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" + /> + )} +
+
+ + {/* BREAK EVEN & TIMES */} +
+
+
+ + Mover Stop pro ponto de entrada +
+ +
+
+
+
+ + setTradingHoursStart(e.target.value)} + onBlur={handleHoursBlur} + 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" + /> +
+
+ + setTradingHoursEnd(e.target.value)} + onBlur={handleHoursBlur} + 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" + /> +
+
+
+

@@ -521,9 +684,15 @@ export const AutoTradingView: React.FC<{ userData?: any, onUpdate?: (data: any)
{/* BROKER INTEGRATION (MT4/MT5) */} -
-

- MT4 / MT5 Server Connection +
+ {/* HOLOGRAPHIC SCAN LINES */} +
+
+

+
+ +
+ EXTERNAL UPLINK (BROKER)

{userData?.mtPlatform === 'deriv_api' && userData?.mtLogin ? (