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.