feat: add global Deriv configuration and improve data parsing
- Added global Deriv token and stake settings to Admin Dashboard. - Enhanced symbol normalization in the Yahoo Finance worker. - Refined error logging across services and added a trade diagnostic test script.
This commit is contained in:
@@ -24,15 +24,13 @@ async function startServer() {
|
||||
|
||||
// --- BACKGROUND WORKER NODE (CRON JOB) ---
|
||||
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('XAU')) return 'GC=F';
|
||||
if (pair.includes('US100')) return 'NQ=F';
|
||||
if (pair.includes('US30')) return 'YM=F';
|
||||
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';
|
||||
}
|
||||
|
||||
// Clean punctuation and common broker suffixes
|
||||
const cleanPair = pair.toString().replace('/', '').replace(/\.M$/, '').replace(/\.std$/, '');
|
||||
return cleanPair + '=X';
|
||||
}
|
||||
|
||||
@@ -80,8 +78,8 @@ const sendTelegramAlertServer = async (text: string, botToken: string, chatId: s
|
||||
const result = yfRes.data.chart.result[0];
|
||||
currentPrice = parseFloat(result.meta.regularMarketPrice);
|
||||
}
|
||||
} catch(e) {
|
||||
console.error("Yahoo Finance data fetch error:", e);
|
||||
} catch(e: any) {
|
||||
console.error("Yahoo Finance data fetch error:", e.message);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -304,6 +302,80 @@ const sendTelegramAlertServer = async (text: string, botToken: string, chatId: s
|
||||
stopLoss: stopLoss.toFixed(5),
|
||||
};
|
||||
|
||||
// --- 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);
|
||||
await addDoc(collection(db, "global_auto_trades"), {
|
||||
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);
|
||||
}
|
||||
|
||||
// --- INÍCIO DA LÓGICA DE AUTO TRADING: META-API ---
|
||||
try {
|
||||
const usersSnap = await getDocs(collection(db, "users"));
|
||||
@@ -331,8 +403,8 @@ const sendTelegramAlertServer = async (text: string, botToken: string, chatId: s
|
||||
throw new Error("Token Deriv inválido. Abortando trade.");
|
||||
}
|
||||
const derivSymbolMap: any = {
|
||||
'Boom 1000 Index': 'BOOM500',
|
||||
'Crash 1000 Index': 'CRASH500',
|
||||
'Boom 1000 Index': 'BOOM1000',
|
||||
'Crash 1000 Index': 'CRASH1000',
|
||||
'Boom 500 Index': 'BOOM500',
|
||||
'Crash 500 Index': 'CRASH500',
|
||||
'Volatility 75 Index': 'R_75',
|
||||
@@ -431,7 +503,7 @@ const sendTelegramAlertServer = async (text: string, botToken: string, chatId: s
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(apiErr) {
|
||||
} catch(apiErr: any) {
|
||||
console.error(`[AutoTrading] Erro geral ao buscar contas da base de dados.`, apiErr);
|
||||
}
|
||||
// --- FIM DA LÓGICA DE AUTO TRADING ---
|
||||
@@ -549,6 +621,26 @@ const sendTelegramAlertServer = async (text: string, botToken: string, chatId: s
|
||||
}, 8000);
|
||||
});
|
||||
|
||||
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 });
|
||||
}
|
||||
});
|
||||
|
||||
app.post("/api/admin/test-auto-trading", async (req, res) => {
|
||||
const { uid, pair, decision, price, metaApiAccountId, settings, mtPlatform, mtLogin } = req.body;
|
||||
try {
|
||||
@@ -647,27 +739,29 @@ const sendTelegramAlertServer = async (text: string, botToken: string, chatId: s
|
||||
app.get("/api/twelve/quote", async (req, res) => {
|
||||
const symbol = req.query.symbol || "EUR/USD";
|
||||
try {
|
||||
if (symbol.toString().includes('Index')) {
|
||||
if (symbol.toString().includes('Index') || symbol.toString().includes('BOOM') || symbol.toString().includes('CRASH')) {
|
||||
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) {
|
||||
console.error("Error fetching proxy quote", error);
|
||||
res.status(500).json({ error: "Failed to fetch market data" });
|
||||
} catch (error: any) {
|
||||
console.error(`Error fetching proxy quote for ${symbol}:`, error.message);
|
||||
// Fallback
|
||||
res.json({ close: "1.000" });
|
||||
}
|
||||
});
|
||||
|
||||
// Proxy for history mapped to Twelve format
|
||||
app.get("/api/twelve/history", async (req, res) => {
|
||||
const symbol = req.query.symbol as string;
|
||||
let symbolYF = "";
|
||||
try {
|
||||
if (symbol.toString().includes('Index')) {
|
||||
return res.status(500).json({ error: "Not supported for index" });
|
||||
if (symbol.toString().includes('Index') || symbol.toString().includes('BOOM') || symbol.toString().includes('CRASH')) {
|
||||
return res.json({ values: [] });
|
||||
}
|
||||
let symbolYF = getYfSymbol(symbol.toString());
|
||||
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];
|
||||
@@ -691,9 +785,9 @@ const sendTelegramAlertServer = async (text: string, botToken: string, chatId: s
|
||||
} else {
|
||||
res.json({ values: [] });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching proxy History", error);
|
||||
res.status(500).json({ error: "Failed to fetch market history" });
|
||||
} catch (error: any) {
|
||||
console.error(`Error fetching proxy History for ${symbol}:`, error.message);
|
||||
res.json({ values: [] });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -39,6 +39,9 @@ export const AdminDashboard: React.FC = () => {
|
||||
|
||||
const [telegramBotToken, setTelegramBotToken] = useState('');
|
||||
const [telegramChatId, setTelegramChatId] = useState('');
|
||||
|
||||
const [globalDerivToken, setGlobalDerivToken] = useState('');
|
||||
const [globalTradeStake, setGlobalTradeStake] = useState(1);
|
||||
|
||||
useEffect(() => {
|
||||
if (autoScannerActive) {
|
||||
@@ -101,6 +104,8 @@ export const AdminDashboard: React.FC = () => {
|
||||
setTelegramChatId(docSnap.data().telegramChatId || '');
|
||||
setAutoScannerActive(docSnap.data().autoScannerActive || false);
|
||||
setAutoScannerInterval(docSnap.data().autoScannerInterval || 15);
|
||||
setGlobalDerivToken(docSnap.data().globalDerivToken || '');
|
||||
setGlobalTradeStake(docSnap.data().globalTradeStake || 1);
|
||||
}
|
||||
}, (error) => {
|
||||
console.warn("Failed to listen to settings:", error);
|
||||
@@ -124,7 +129,9 @@ export const AdminDashboard: React.FC = () => {
|
||||
telegramBotToken,
|
||||
telegramChatId,
|
||||
autoScannerActive,
|
||||
autoScannerInterval
|
||||
autoScannerInterval,
|
||||
globalDerivToken,
|
||||
globalTradeStake
|
||||
}, { merge: true });
|
||||
alert('Configurações salvas com sucesso!');
|
||||
} catch (err) {
|
||||
@@ -389,6 +396,37 @@ export const AdminDashboard: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4 bg-black/20 p-4 rounded-xl border border-white/5">
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<span className="text-sm font-bold text-white uppercase tracking-wider">Super Administrator - Global AI Trading</span>
|
||||
</div>
|
||||
<p className="text-xs text-zinc-400 mb-4">
|
||||
Configure a conta Master (Deriv API Token) onde a Inteligência Artificial fará a execução automática e permanente de todas as Smart Entries geradas.
|
||||
</p>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="text-[10px] font-black uppercase text-zinc-500 tracking-widest block mb-1">Global Deriv API Token (Master)</label>
|
||||
<input
|
||||
type="text"
|
||||
value={globalDerivToken}
|
||||
onChange={(e) => setGlobalDerivToken(e.target.value)}
|
||||
className="bg-white/5 border border-white/10 rounded-lg p-2.5 text-white outline-none w-full text-xs font-mono"
|
||||
placeholder="Cole o token da Deriv aqui"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-[10px] font-black uppercase text-zinc-500 tracking-widest block mb-1">Stake Base por Operação (USD)</label>
|
||||
<input
|
||||
type="number"
|
||||
step="0.01"
|
||||
value={globalTradeStake}
|
||||
onChange={(e) => setGlobalTradeStake(parseFloat(e.target.value) || 1)}
|
||||
className="bg-white/5 border border-white/10 rounded-lg p-2.5 text-white outline-none w-full text-xs font-mono"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
|
||||
@@ -149,8 +149,8 @@ export const checkHistoricalSignalResult = async (signal: Signal): Promise<strin
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Historical check error", e);
|
||||
} catch (e: any) {
|
||||
console.error("Historical check error:", e.message);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
@@ -227,8 +227,8 @@ export const fetchChartData = async (symbol: string): Promise<{ time: number, op
|
||||
close: parseFloat(candle.close)
|
||||
}));
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("Error fetching chart data", e);
|
||||
} catch (e: any) {
|
||||
console.warn("Error fetching chart data:", e.message);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
@@ -321,8 +321,8 @@ export const fetchCurrentPrice = async (symbol: string): Promise<number | null>
|
||||
const price = parseFloat(response.data.close);
|
||||
if (!isNaN(price)) return price;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error fetching TWELVE DATA price", e);
|
||||
} catch (e: any) {
|
||||
console.error("Error fetching TWELVE DATA price:", e.message);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { db } from "./src/lib/firebase";
|
||||
import { collection, getDocs, query, where } from "firebase/firestore";
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
const usersSnap = await getDocs(query(collection(db, 'users'), where('email', '==', 'desartstudiopro@gmail.com')));
|
||||
if (usersSnap.empty) {
|
||||
console.log('User not found');
|
||||
return;
|
||||
}
|
||||
const user = usersSnap.docs[0];
|
||||
console.log(`Checking auto_trades for user ${user.id}`);
|
||||
const tradesSnap = await getDocs(collection(db, 'users', user.id, 'auto_trades'));
|
||||
if (tradesSnap.empty) {
|
||||
console.log('No trades found for this user.');
|
||||
} else {
|
||||
console.log('Found ' + tradesSnap.size + ' trades:');
|
||||
tradesSnap.forEach(t => console.log(t.id, t.data()));
|
||||
}
|
||||
process.exit(0);
|
||||
} catch(e) {
|
||||
console.error('Error:', e);
|
||||
process.exit(1);
|
||||
}
|
||||
})();
|
||||
Reference in New Issue
Block a user