From e225cad4b91a0c0c7747eef02394841b10fa59f6 Mon Sep 17 00:00:00 2001 From: desartstudio95 Date: Thu, 7 May 2026 13:24:31 +0200 Subject: [PATCH] feat: Update Gemini model and improve error handling Switches to the `gemini-2.5-flash` model for potentially faster and more cost-effective analysis. Enhances error handling in chart analysis to provide more specific user feedback for API rate limits and credit exhaustion. Adds UI elements for signal history expansion and introduces pair selection for dashboard statistics, improving user interaction and data visualization. --- src/components/AnalysisView.tsx | 6 +- src/components/DashboardStats.tsx | 119 ++++++++++++++++---- src/components/SignalHistory.tsx | 178 +++++++++++++++++++----------- src/services/geminiService.ts | 2 +- 4 files changed, 221 insertions(+), 84 deletions(-) diff --git a/src/components/AnalysisView.tsx b/src/components/AnalysisView.tsx index a476f88..ed4aead 100644 --- a/src/components/AnalysisView.tsx +++ b/src/components/AnalysisView.tsx @@ -93,7 +93,11 @@ export const AnalysisView: React.FC<{ userData?: any }> = ({ userData }) => { } catch (err: any) { console.error(err); - setError(err.message || 'Falha ao analisar imagem. Verifique se o gráfico está claro.'); + let errorMessage = err.message || 'Falha ao analisar imagem. Verifique se o gráfico está claro.'; + if (errorMessage.includes('429') || errorMessage.includes('RESOURCE_EXHAUSTED') || errorMessage.includes('prepayment credits')) { + errorMessage = 'Sua cota de uso da API do Google Gemini (IA) foi excedida ou os créditos acabaram. Por favor, acesse o painel do Google AI Studio (https://ai.studio) para verificar seu faturamento e recarregar os créditos.'; + } + setError(errorMessage); } finally { setIsAnalyzing(false); } diff --git a/src/components/DashboardStats.tsx b/src/components/DashboardStats.tsx index 41b9322..626b715 100644 --- a/src/components/DashboardStats.tsx +++ b/src/components/DashboardStats.tsx @@ -3,7 +3,8 @@ import { Signal, SignalResult, SignalType } from '../types'; import { motion } from 'motion/react'; import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, - BarChart, Bar, Cell + BarChart, Bar, Cell, + LineChart, Line, Legend } from 'recharts'; import { TrendingUp, Award, Target, Activity } from 'lucide-react'; import { collection, query, where, orderBy, onSnapshot } from 'firebase/firestore'; @@ -12,6 +13,7 @@ import { auth, db, handleFirestoreError, OperationType } from '../lib/firebase'; export const DashboardStats: React.FC = () => { const [signals, setSignals] = useState([]); const [loading, setLoading] = useState(true); + const [selectedPair, setSelectedPair] = useState(''); useEffect(() => { if (!auth.currentUser) return; @@ -36,11 +38,37 @@ export const DashboardStats: React.FC = () => { return () => unsubscribe(); }, []); + useEffect(() => { + if (!selectedPair && signals.length > 0) { + const pairs = Array.from(new Set(signals.filter(s => s.pair).map(s => s.pair))); + if (pairs.length > 0) { + setSelectedPair(pairs[0]); + } + } + }, [signals, selectedPair]); + const totalSignals = signals.length; - // const gains = signals.filter(s => s.result === SignalResult.GAIN).length; - // const winRate = totalSignals > 0 ? (gains / totalSignals) * 100 : 0; - // User requested 90% precision + const gains = signals.filter(s => s.result === SignalResult.GAIN).length; + const losses = signals.filter(s => s.result === SignalResult.LOSS).length; + const completedSignals = gains + losses; + const winRate = completedSignals > 0 ? (gains / completedSignals) * 100 : 0; + const profitLossRatio = losses > 0 ? (gains / losses).toFixed(2) : (gains > 0 ? '∞' : '0'); + + const pairStats = signals.reduce((acc: any[], signal) => { + const existing = acc.find(i => i.name === signal.pair); + if (existing) { + existing.total += 1; + if (signal.result === SignalResult.GAIN) existing.gains += 1; + } else { + acc.push({ name: signal.pair, total: 1, gains: signal.result === SignalResult.GAIN ? 1 : 0 }); + } + return acc; + }, []).map(p => ({ ...p, winRate: Math.round((p.gains / p.total) * 100) })); + + const bestAssetEntry = [...pairStats].sort((a,b) => b.winRate - a.winRate)[0]; + const bestAsset = bestAssetEntry ? `${bestAssetEntry.name} (${bestAssetEntry.winRate}%)` : 'N/A'; + const chartData = signals .sort((a, b) => a.timestamp - b.timestamp) .reduce((acc: any[], signal, index) => { @@ -57,22 +85,27 @@ export const DashboardStats: React.FC = () => { const existing = acc.find(i => i.name === signal.timeframe); if (existing) { existing.count += 1; - } else { - acc.push({ name: signal.timeframe, count: 1 }); - } - return acc; - }, []); - - const pairStats = signals.reduce((acc: any[], signal) => { - const existing = acc.find(i => i.name === signal.pair); - if (existing) { - existing.total += 1; if (signal.result === SignalResult.GAIN) existing.gains += 1; } else { - acc.push({ name: signal.pair, total: 1, gains: signal.result === SignalResult.GAIN ? 1 : 0 }); + acc.push({ name: signal.timeframe, count: 1, gains: signal.result === SignalResult.GAIN ? 1 : 0 }); } return acc; - }, []).map(p => ({ ...p, winRate: Math.round((p.gains / p.total) * 100) })); + }, []).map((t: any) => ({ ...t, winRate: Math.round((t.gains / t.count) * 100) })); + + const bestTimeframeEntry = [...timeframeStats].sort((a,b) => b.winRate - a.winRate)[0]; + const bestTimeframe = bestTimeframeEntry ? `${bestTimeframeEntry.name} (${bestTimeframeEntry.winRate}%)` : 'N/A'; + + const priceHistoryData = signals + .filter(s => s.type !== SignalType.WAIT && parseFloat(s.entry) && parseFloat(s.takeProfit) && parseFloat(s.stopLoss) && s.pair === selectedPair) + .sort((a, b) => a.timestamp - b.timestamp) + .map(s => ({ + name: new Date(s.timestamp).toLocaleDateString('pt-BR', { day: '2-digit', month: '2-digit' }), + entry: parseFloat(s.entry.replace(/[^0-9.-]/g, '')), + stopLoss: parseFloat(s.stopLoss.replace(/[^0-9.-]/g, '')), + takeProfit: parseFloat(s.takeProfit.replace(/[^0-9.-]/g, '')), + })); + + const availablePairs = Array.from(new Set(signals.filter(s => s.pair).map(s => s.pair))); if (loading) return null; @@ -96,9 +129,9 @@ export const DashboardStats: React.FC = () => { > {[ { label: 'Total Sinais', value: totalSignals, icon: Activity }, - { label: 'Taxa de Acerto', value: `90.0%`, icon: Award }, - { label: 'Melhor Ativo', value: [...pairStats].sort((a,b) => b.winRate - a.winRate)[0]?.name || 'N/A', icon: Target }, - { label: 'IA Learning', value: '+14%', icon: TrendingUp, positive: true }, + { label: 'Taxa de Acerto', value: `${winRate.toFixed(1)}%`, icon: Award }, + { label: 'Ratio Win/Loss', value: profitLossRatio, icon: Target }, + { label: 'Melhor Ativo', value: bestAsset, icon: TrendingUp, positive: true }, ].map((stat, i) => ( { ))} +
+
+

Histórico de Preços dos Sinais

+ {availablePairs.length > 0 && ( + + )} +
+
+ + + + + + + + + + + + +
+
+

Curva de Equidade Estimada

@@ -234,3 +314,4 @@ export const DashboardStats: React.FC = () => {
); }; + diff --git a/src/components/SignalHistory.tsx b/src/components/SignalHistory.tsx index e026a4b..65e9320 100644 --- a/src/components/SignalHistory.tsx +++ b/src/components/SignalHistory.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from 'react'; import { Signal, SignalResult, SignalType } from '../types'; import { cn } from '../lib/utils'; -import { Clock, TrendingUp, TrendingDown } from 'lucide-react'; +import { Clock, TrendingUp, TrendingDown, ChevronDown, ChevronUp } from 'lucide-react'; import { collection, query, where, orderBy, onSnapshot } from 'firebase/firestore'; import { auth, db, handleFirestoreError, OperationType } from '../lib/firebase'; @@ -10,6 +10,7 @@ export const SignalHistory: React.FC = () => { const [loading, setLoading] = useState(true); const [startDate, setStartDate] = useState(''); const [endDate, setEndDate] = useState(''); + const [expandedSignalIds, setExpandedSignalIds] = useState([]); useEffect(() => { if (!auth.currentUser) return; @@ -68,6 +69,12 @@ export const SignalHistory: React.FC = () => { return true; }); + const toggleSignal = (id: string) => { + setExpandedSignalIds(prev => + prev.includes(id) ? prev.filter(i => i !== id) : [...prev, id] + ); + }; + return (
@@ -111,74 +118,119 @@ export const SignalHistory: React.FC = () => { Nenhum sinal encontrado. Comece realizando um novo scan.
) : ( - filteredSignals.map((signal) => ( -
-
- {signal.type === SignalType.BUY ? : } -
- -
-
- Ativo / TF -

{signal.pair} · {signal.timeframe}

-
-
- Tipo -

{signal.type}

-
-
- Score -

{signal.score}%

-
-
- SL / TP -

{signal.stopLoss} / {signal.takeProfit}

-
-
- Data e Hora -

{new Date(signal.timestamp).toLocaleString("pt-BR", { dateStyle: "short", timeStyle: "short" })}

-
-
- Status + filteredSignals.map((signal) => { + const isExpanded = expandedSignalIds.includes(signal.id); + return ( +
toggleSignal(signal.id)} + > +
- {signal.result === SignalResult.PENDING ? "Válido" : "Expirado"} + {signal.type === SignalType.BUY ? : } +
+ +
+
+ Ativo / TF +

{signal.pair} · {signal.timeframe}

+
+
+ Tipo +

{signal.type}

+
+
+ Score +

{signal.score}%

+
+
+ SL / TP +

{signal.stopLoss} / {signal.takeProfit}

+
+
+ Data e Hora +

{new Date(signal.timestamp).toLocaleString("pt-BR", { dateStyle: "short", timeStyle: "short" })}

+
+
+ Status +
+ {signal.result === SignalResult.PENDING ? "Válido" : "Expirado"} +
+
+
+ Resultado +
+ {signal.result} +
+
+
+ +
+ +
+
+ Detalhes +
+ {isExpanded ? : }
-
- Resultado -
- {signal.result} + + {isExpanded && ( +
+
+ {signal.justification && ( +
+

Justificativa

+

{signal.justification}

+
+ )} + {signal.analiseGeral && ( +
+

Análise Geral

+

{signal.analiseGeral}

+
+ )} +
+
+ {signal.estrutura && ( +
+

Estrutura de Mercado

+

{signal.estrutura}

+
+ )} + {signal.tecnica && ( +
+

Análise Técnica

+

{signal.tecnica}

+
+ )} + {signal.fundamental && ( +
+

Análise Fundamental

+

{signal.fundamental}

+
+ )} +
-
+ )}
- -
- -
- Data - - {new Date(signal.timestamp).toLocaleDateString()} - - - {new Date(signal.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })} - -
-
- )) + ); + }) )}
diff --git a/src/services/geminiService.ts b/src/services/geminiService.ts index b0f2fdd..8819dda 100644 --- a/src/services/geminiService.ts +++ b/src/services/geminiService.ts @@ -4,7 +4,7 @@ import { AnalysisResponse, SignalType } from "../types"; const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY || '' }); export const analyzeForexChart = async (imageBase64: string, userNotes?: string, preferredMode?: 'Técnico' | 'Fundamental' | 'Híbrido'): Promise => { - const model = "gemini-3.1-pro-preview"; + const model = "gemini-2.5-flash"; const systemInstruction = ` Você é o QuantScan IA, um sistema avançado de análise de mercado financeiro com inteligência institucional.