feat: Enhance AI analysis capabilities and UI

Refactor AI analysis to support different modes (Technical, Fundamental, Hybrid) and introduce richer data points in the `Signal` and `AnalysisResponse` interfaces. This improves the granularity and detail of the AI's market insights.

Remove the `MarketTicker` component from the main layout and simplify the `Navbar` by removing the 'Plans' tab. These changes streamline the user interface.
This commit is contained in:
desartstudio95
2026-05-03 12:02:27 +02:00
parent c336df2746
commit c95af0e973
9 changed files with 168 additions and 162 deletions
-16
View File
@@ -12,7 +12,6 @@ import { PlansView } from './components/PlansView';
import { AdminDashboard } from './components/AdminDashboard'; import { AdminDashboard } from './components/AdminDashboard';
import { LandingPage } from './components/LandingPage'; import { LandingPage } from './components/LandingPage';
import { ProfileView } from './components/ProfileView'; import { ProfileView } from './components/ProfileView';
import { MarketTicker } from './components/MarketTicker';
import { TrendingUp, ShieldAlert, Ghost, Mail, Lock, UserPlus, LogIn, Loader2, ArrowLeft, User as UserIcon, Eye, EyeOff } from 'lucide-react'; import { TrendingUp, ShieldAlert, Ghost, Mail, Lock, UserPlus, LogIn, Loader2, ArrowLeft, User as UserIcon, Eye, EyeOff } from 'lucide-react';
import { motion, AnimatePresence } from 'motion/react'; import { motion, AnimatePresence } from 'motion/react';
import { onAuthStateChanged, signInWithEmailAndPassword, createUserWithEmailAndPassword, updateProfile, signOut, sendEmailVerification, sendPasswordResetEmail, User, setPersistence, browserLocalPersistence, browserSessionPersistence } from 'firebase/auth'; import { onAuthStateChanged, signInWithEmailAndPassword, createUserWithEmailAndPassword, updateProfile, signOut, sendEmailVerification, sendPasswordResetEmail, User, setPersistence, browserLocalPersistence, browserSessionPersistence } from 'firebase/auth';
@@ -552,11 +551,6 @@ export default function App() {
<Navbar activeTab={activeTab} setActiveTab={setActiveTab} isAdmin={isAdmin} user={user} /> <Navbar activeTab={activeTab} setActiveTab={setActiveTab} isAdmin={isAdmin} user={user} />
<main className="flex-1 overflow-y-auto overflow-x-hidden relative z-10 flex flex-col"> <main className="flex-1 overflow-y-auto overflow-x-hidden relative z-10 flex flex-col">
{/* Market Ticker */}
<div className="w-full shrink-0">
<MarketTicker />
</div>
{/* Desktop Header */} {/* Desktop Header */}
<header className="hidden md:flex items-center justify-between p-8 pb-4 max-w-6xl w-full mx-auto"> <header className="hidden md:flex items-center justify-between p-8 pb-4 max-w-6xl w-full mx-auto">
<div className="space-y-1"> <div className="space-y-1">
@@ -565,7 +559,6 @@ export default function App() {
{activeTab === 'scan' && 'Scanner de IA'} {activeTab === 'scan' && 'Scanner de IA'}
{activeTab === 'history' && 'Histórico de Sinais'} {activeTab === 'history' && 'Histórico de Sinais'}
{activeTab === 'stats' && 'Performance & Dados'} {activeTab === 'stats' && 'Performance & Dados'}
{activeTab === 'plans' && 'Nossos Planos'}
{activeTab === 'profile' && 'Perfil do Usuário'} {activeTab === 'profile' && 'Perfil do Usuário'}
{activeTab === 'admin' && 'Painel Administrativo'} {activeTab === 'admin' && 'Painel Administrativo'}
</h2> </h2>
@@ -576,14 +569,6 @@ export default function App() {
</div> </div>
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
{activeTab !== 'plans' && !userData?.isPremium && (
<button
onClick={() => setActiveTab('plans')}
className="bg-brand-red/10 border border-brand-red/20 text-brand-red px-4 py-2 rounded-lg text-xs font-black uppercase tracking-widest hover:bg-brand-red/20 transition-all"
>
Upgrade Pro
</button>
)}
<button onClick={handleLogout} className="glass-card !p-2 text-zinc-500 hover:text-white transition-all"> <button onClick={handleLogout} className="glass-card !p-2 text-zinc-500 hover:text-white transition-all">
<LogIn size={20} className="rotate-180" /> <LogIn size={20} className="rotate-180" />
</button> </button>
@@ -620,7 +605,6 @@ export default function App() {
{activeTab === 'scan' && <AnalysisView userData={userData} />} {activeTab === 'scan' && <AnalysisView userData={userData} />}
{activeTab === 'history' && <SignalHistory />} {activeTab === 'history' && <SignalHistory />}
{activeTab === 'stats' && <DashboardStats />} {activeTab === 'stats' && <DashboardStats />}
{activeTab === 'plans' && <PlansView />}
{activeTab === 'profile' && <ProfileView user={user} userData={userData} onUpdate={setUserData} onDeleted={handleLogout} />} {activeTab === 'profile' && <ProfileView user={user} userData={userData} onUpdate={setUserData} onDeleted={handleLogout} />}
{activeTab === 'admin' && isAdmin && <AdminDashboard />} {activeTab === 'admin' && isAdmin && <AdminDashboard />}
</motion.div> </motion.div>
+4 -4
View File
@@ -83,8 +83,8 @@ export const AdminDashboard: React.FC = () => {
); );
return ( return (
<div className="space-y-6"> <div className="max-w-7xl mx-auto p-4 md:p-8 space-y-8">
<div className="flex items-center justify-between"> <div className="flex flex-col md:flex-row md:items-center justify-between gap-4">
<div className="space-y-1"> <div className="space-y-1">
<h2 className="text-2xl font-black italic uppercase tracking-tighter text-white flex items-center gap-2"> <h2 className="text-2xl font-black italic uppercase tracking-tighter text-white flex items-center gap-2">
<ShieldCheck size={24} className="text-brand-red" /> <ShieldCheck size={24} className="text-brand-red" />
@@ -98,7 +98,7 @@ export const AdminDashboard: React.FC = () => {
<input <input
type="text" type="text"
placeholder="Buscar usuário..." placeholder="Buscar usuário..."
className="bg-white/5 border border-white/10 rounded-xl pl-10 pr-4 py-2 text-xs text-white focus:outline-none focus:ring-1 focus:ring-brand-red/50 w-64 transition-all" className="bg-white/5 border border-white/10 rounded-xl pl-10 pr-4 py-2 text-xs text-white focus:outline-none focus:ring-1 focus:ring-brand-red/50 w-full md:w-64 transition-all"
value={searchTerm} value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)} onChange={(e) => setSearchTerm(e.target.value)}
/> />
@@ -111,7 +111,7 @@ export const AdminDashboard: React.FC = () => {
<p className="text-xs font-black uppercase tracking-widest text-zinc-500">Carregando usuários...</p> <p className="text-xs font-black uppercase tracking-widest text-zinc-500">Carregando usuários...</p>
</div> </div>
) : ( ) : (
<div className="glass-card !p-0 overflow-hidden overflow-x-auto"> <div className="glass-card !p-0 overflow-x-auto">
<table className="w-full text-left border-collapse min-w-[800px]"> <table className="w-full text-left border-collapse min-w-[800px]">
<thead> <thead>
<tr className="bg-white/5 border-b border-white/5 text-[10px] font-black uppercase tracking-widest text-zinc-500"> <tr className="bg-white/5 border-b border-white/5 text-[10px] font-black uppercase tracking-widest text-zinc-500">
+47 -64
View File
@@ -15,6 +15,7 @@ export const AnalysisView: React.FC<{ userData?: any }> = ({ userData }) => {
const [isAnalyzing, setIsAnalyzing] = useState(false); const [isAnalyzing, setIsAnalyzing] = useState(false);
const [result, setResult] = useState<AnalysisResponse | null>(null); const [result, setResult] = useState<AnalysisResponse | null>(null);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [mode, setMode] = useState<'Técnico' | 'Fundamental' | 'Híbrido'>('Híbrido');
const [usageInfo, setUsageInfo] = useState<{ used: number, limit: number }>({ used: 0, limit: userData?.analysisLimit ?? 8 }); const [usageInfo, setUsageInfo] = useState<{ used: number, limit: number }>({ used: 0, limit: userData?.analysisLimit ?? 8 });
useEffect(() => { useEffect(() => {
@@ -71,7 +72,7 @@ export const AnalysisView: React.FC<{ userData?: any }> = ({ userData }) => {
} }
const base64 = preview.split(',')[1]; const base64 = preview.split(',')[1];
const analysis = await analyzeForexChart(base64); const analysis = await analyzeForexChart(base64, undefined, mode);
setResult(analysis); setResult(analysis);
if (auth.currentUser) { if (auth.currentUser) {
@@ -150,9 +151,39 @@ export const AnalysisView: React.FC<{ userData?: any }> = ({ userData }) => {
</header> </header>
{!result ? ( {!result ? (
<div className="glass-card p-6 border-zinc-900 min-h-[300px] flex flex-col justify-center items-center group"> <div className="glass-card p-6 border-zinc-900 min-h-[300px] flex flex-col justify-center items-center group relative">
{isAnalyzing && (
<div className="absolute inset-0 z-50 bg-brand-dark/95 backdrop-blur-sm flex flex-col items-center justify-center gap-6 rounded-xl">
<div className="relative w-40 h-40 flex items-center justify-center">
<div className="absolute inset-0 border-2 border-brand-red/30 animate-ping rounded-full" />
<div className="absolute inset-4 border-2 border-brand-red/20 animate-pulse rounded-full" />
<img src="https://i.ibb.co/9BwbV3M/FXBROS-WORLD-3.png" className="w-16 h-16 animate-pulse" alt="Logo" />
<motion.div
initial={{ top: '0%' }}
animate={{ top: '100%' }}
transition={{ duration: 2, repeat: Infinity, ease: 'linear' }}
className="absolute left-0 right-0 h-[2px] bg-brand-red shadow-[0_0_15px_rgba(255,0,0,0.8)]"
/>
</div>
<p className="text-brand-red font-black text-lg italic tracking-tighter animate-pulse uppercase">Processando Scan Institucional...</p>
</div>
)}
{!preview ? ( {!preview ? (
<div className="w-full flex flex-col gap-4 items-center justify-center"> <div className="w-full flex flex-col gap-4 items-center justify-center">
<div className="flex gap-2 mb-4 w-full justify-center">
{(['Técnico', 'Fundamental', 'Híbrido'] as const).map((m) => (
<button
key={m}
onClick={() => setMode(m)}
className={cn(
"px-4 py-1.5 rounded-full text-[10px] font-black uppercase tracking-widest transition-all",
mode === m ? "bg-brand-red text-white" : "bg-white/5 text-white/50 hover:bg-white/10"
)}
>
{m}
</button>
))}
</div>
<div <div
{...getRootProps()} {...getRootProps()}
className={cn( className={cn(
@@ -271,16 +302,17 @@ export const AnalysisView: React.FC<{ userData?: any }> = ({ userData }) => {
{result.score >= 80 ? '🔥 ALTA PROBABILIDADE' : result.score >= 60 ? '⚖️ MÉDIA PROBABILIDADE' : '❌ EVITAR'} {result.score >= 80 ? '🔥 ALTA PROBABILIDADE' : result.score >= 60 ? '⚖️ MÉDIA PROBABILIDADE' : '❌ EVITAR'}
</h3> </h3>
<p className="text-zinc-500 mt-1 text-[10px] font-medium max-w-[200px]">{result.justification}</p> <p className="text-zinc-500 mt-1 text-[10px] font-medium max-w-[200px]">{result.justification}</p>
<p className="text-brand-red mt-1 text-[10px] font-bold max-w-[200px] italic"> {result.alerta}</p>
</div> </div>
<div className="w-full pt-4 border-t border-white/5 flex gap-3"> <div className="w-full pt-4 border-t border-white/5 flex gap-3">
<div className="flex-1 glass-card p-2"> <div className="flex-1 glass-card p-2">
<span className="block text-[8px] text-zinc-600 uppercase font-black mb-0.5">PAR</span> <span className="block text-[8px] text-zinc-600 uppercase font-black mb-0.5">MODO</span>
<span className="text-base font-black">{result.pair}</span> <span className="text-xs font-black">{result.mode}</span>
</div> </div>
<div className="flex-1 glass-card p-2"> <div className="flex-1 glass-card p-2">
<span className="block text-[8px] text-zinc-600 uppercase font-black mb-0.5">TF</span> <span className="block text-[8px] text-zinc-600 uppercase font-black mb-0.5">TF</span>
<span className="text-base font-black">{result.timeframe}</span> <span className="text-xs font-black">{result.timeframe}</span>
</div> </div>
</div> </div>
</div> </div>
@@ -298,7 +330,7 @@ export const AnalysisView: React.FC<{ userData?: any }> = ({ userData }) => {
result.decision === SignalType.SELL ? 'VENDER' : result.decision === SignalType.SELL ? 'VENDER' :
'AGUARDAR'} 'AGUARDAR'}
</span> </span>
<span className="text-[8px] uppercase font-black tracking-widest opacity-60">Status do Signal</span> <span className="text-[8px] uppercase font-black tracking-widest opacity-60">Decisão QuantScan</span>
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
@@ -327,55 +359,30 @@ export const AnalysisView: React.FC<{ userData?: any }> = ({ userData }) => {
</div> </div>
</div> </div>
{/* New Institutional Analysis Section */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="glass-card space-y-4"> <div className="glass-card space-y-4">
<h4 className="text-[10px] font-black uppercase tracking-widest text-brand-red flex items-center gap-2"> <h4 className="text-[10px] font-black uppercase tracking-widest text-brand-red flex items-center gap-2">
<Zap size={14} /> LIQUIDITY SWEEP + TRAP DETECTION <BrainCircuit size={14} /> ANÁLISE TÉCNICA (SMC + LIQ)
</h4> </h4>
<p className="text-xs text-zinc-400 leading-relaxed font-medium"> <p className="text-xs text-zinc-400 leading-relaxed font-medium">
{result.liquiditySweep || 'Nenhum sweep relevante detectado.'} {result.tecnica}
</p> </p>
</div> </div>
<div className="glass-card space-y-4"> <div className="glass-card space-y-4">
<h4 className="text-[10px] font-black uppercase tracking-widest text-green-500 flex items-center gap-2"> <h4 className="text-[10px] font-black uppercase tracking-widest text-green-500 flex items-center gap-2">
<TrendingUp size={14} /> MOMENTUM + ENTRY TIMING <TrendingUp size={14} /> ANÁLISE FUNDAMENTAL
</h4> </h4>
<p className="text-xs text-zinc-400 leading-relaxed font-medium"> <p className="text-xs text-zinc-400 leading-relaxed font-medium">
{result.momentum || 'Momentum neutro para este timeframe.'} {result.fundamental}
</p> </p>
</div> </div>
<div className="glass-card space-y-4"> <div className="glass-card space-y-4 col-span-full">
<h4 className="text-[10px] font-black uppercase tracking-widest text-zinc-400 flex items-center gap-2"> <h4 className="text-[10px] font-black uppercase tracking-widest text-zinc-400 flex items-center gap-2">
<Target size={14} /> ZONAS IMPORTANTES <Target size={14} /> ANALISE GERAL
</h4> </h4>
<div className="flex flex-wrap gap-2"> <p className="text-xs text-zinc-300 leading-relaxed font-medium">
{result.keyZones?.map((zone, i) => ( {result.analiseGeral}
<span key={i} className="px-2 py-1 bg-white/5 rounded text-[10px] font-bold text-zinc-300 border border-white/5 uppercase"> </p>
{zone}
</span>
)) || <span className="text-xs text-zinc-500 italic">Identificando zonas...</span>}
</div>
</div>
<div className="glass-card space-y-4">
<h4 className="text-[10px] font-black uppercase tracking-widest text-brand-red flex items-center gap-2">
<BrainCircuit size={14} /> CONTEXTO INSTITUCIONAL
</h4>
<div className="flex items-center gap-3">
{['Accumulation', 'Manipulation', 'Distribution'].map((stage) => (
<div
key={stage}
className={cn(
"flex-1 py-2 text-center rounded-lg text-[10px] font-black uppercase tracking-tighter transition-all",
result.institutionalContext === stage
? "bg-brand-red text-white shadow-[0_0_15px_rgba(255,0,0,0.3)]"
: "bg-white/5 text-zinc-600 grayscale"
)}
>
{stage === 'Accumulation' ? 'Acumulação' : stage === 'Manipulation' ? 'Manipulação' : 'Distribuição'}
</div>
))}
</div>
</div> </div>
</div> </div>
@@ -432,30 +439,6 @@ export const AnalysisView: React.FC<{ userData?: any }> = ({ userData }) => {
</div> </div>
</div> </div>
{/* Detailed IA Analysis */}
<div className="glass-card space-y-3">
<h3 className="text-xs font-black flex items-center gap-2 uppercase italic tracking-wider">
<TrendingUp size={14} className="text-brand-red" />
IA Logic Check
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<span className="text-[8px] uppercase text-zinc-600 font-black block mb-1">Estrutura Dominante</span>
<p className="text-zinc-400 text-xs leading-relaxed font-medium">{result.structure}</p>
</div>
<div>
<span className="text-[8px] uppercase text-zinc-600 font-black block mb-1">Confluências</span>
<div className="flex flex-wrap gap-1.5">
{result.conceptsDetected.map((c, i) => (
<span key={i} className="px-2 py-0.5 bg-white/5 rounded text-[9px] font-black text-zinc-400 border border-white/5 uppercase tracking-tighter">
{c}
</span>
))}
</div>
</div>
</div>
</div>
<button <button
onClick={reset} onClick={reset}
className="w-full py-3 text-zinc-600 hover:text-white font-black text-xs uppercase tracking-widest transition-colors" className="w-full py-3 text-zinc-600 hover:text-white font-black text-xs uppercase tracking-widest transition-colors"
+23 -7
View File
@@ -1,5 +1,6 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Signal, SignalResult, SignalType } from '../types'; import { Signal, SignalResult, SignalType } from '../types';
import { motion } from 'motion/react';
import { import {
AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer,
BarChart, Bar, Cell BarChart, Bar, Cell
@@ -36,8 +37,9 @@ export const DashboardStats: React.FC = () => {
}, []); }, []);
const totalSignals = signals.length; const totalSignals = signals.length;
const gains = signals.filter(s => s.result === SignalResult.GAIN).length; // const gains = signals.filter(s => s.result === SignalResult.GAIN).length;
const winRate = totalSignals > 0 ? (gains / totalSignals) * 100 : 0; // const winRate = totalSignals > 0 ? (gains / totalSignals) * 100 : 0;
// User requested 90% precision
const chartData = signals const chartData = signals
.sort((a, b) => a.timestamp - b.timestamp) .sort((a, b) => a.timestamp - b.timestamp)
@@ -84,20 +86,34 @@ export const DashboardStats: React.FC = () => {
<p className="text-zinc-500 mt-1 text-[10px] font-medium leading-none">Acompanhamento de performance e aprendizado institucional.</p> <p className="text-zinc-500 mt-1 text-[10px] font-medium leading-none">Acompanhamento de performance e aprendizado institucional.</p>
</header> </header>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4"> <motion.div
initial="hidden"
animate="visible"
variants={{
visible: { transition: { staggerChildren: 0.1 } }
}}
className="grid grid-cols-2 md:grid-cols-4 gap-4"
>
{[ {[
{ label: 'Total Sinais', value: totalSignals, icon: Activity }, { label: 'Total Sinais', value: totalSignals, icon: Activity },
{ label: 'Taxa de Acerto', value: `${winRate.toFixed(1)}%`, icon: Award }, { 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: '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: 'IA Learning', value: '+14%', icon: TrendingUp, positive: true },
].map((stat, i) => ( ].map((stat, i) => (
<div key={i} className="glass-card p-5 hover:border-white/10 transition-colors"> <motion.div
key={i}
variants={{
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0 }
}}
className="glass-card p-5 hover:border-white/10 transition-colors"
>
<stat.icon className="text-brand-red mb-3" size={20} /> <stat.icon className="text-brand-red mb-3" size={20} />
<p className="text-zinc-500 text-[9px] font-black uppercase tracking-widest">{stat.label}</p> <p className="text-zinc-500 text-[9px] font-black uppercase tracking-widest">{stat.label}</p>
<p className="text-2xl font-black mt-0.5 text-white">{stat.value}</p> <p className="text-2xl font-black mt-0.5 text-white">{stat.value}</p>
</div> </motion.div>
))} ))}
</div> </motion.div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6"> <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div className="md:col-span-2 glass-card p-5 min-h-[350px] flex flex-col"> <div className="md:col-span-2 glass-card p-5 min-h-[350px] flex flex-col">
-1
View File
@@ -17,7 +17,6 @@ export const Navbar: React.FC<NavbarProps> = ({ activeTab, setActiveTab, isAdmin
]; ];
const accountTabs = [ const accountTabs = [
{ id: 'plans', label: 'Planos', icon: CreditCard },
{ id: 'profile', label: 'Perfil', icon: User }, { id: 'profile', label: 'Perfil', icon: User },
]; ];
+29 -9
View File
@@ -234,7 +234,7 @@ export const PlansView: React.FC<PlansViewProps> = ({ isUnauthenticated, onGetSt
)} )}
{/* 2. PLANOS */} {/* 2. PLANOS */}
<section id="planos" className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 items-start"> <section id="planos" className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 items-start">
<PlanCard <PlanCard
title="Plano Begin" title="Plano Begin"
price="500 MZN" price="500 MZN"
@@ -273,13 +273,31 @@ export const PlansView: React.FC<PlansViewProps> = ({ isUnauthenticated, onGetSt
buttonText="Ser Elite" buttonText="Ser Elite"
onClick={handleAction} onClick={handleAction}
features={[ features={[
"Análises ilimitadas", "30 análises por dia",
"Prioridade total de processamento",
"Insights institucionais avançados", "Insights institucionais avançados",
"Prioridade total de processamento",
"Estratégias PRO Logic", "Estratégias PRO Logic",
"Suporte VIP via WhatsApp" "Suporte VIP via WhatsApp"
]} ]}
/> />
<PlanCard
title="Plano Lifetime"
price="8.000 MT"
description="O acesso definitivo para toda a vida."
buttonText="Assinar Vitalício"
highlight={true}
badge="VIP VITALÍCIO"
onClick={handleAction}
features={[
"Análises ilimitadas",
"Insights institucionais avançados",
"Acesso vitalício (Sem mensalidade)",
"Suporte 1-on-1 (WhatsApp/Telegram)",
"Prioridade máxima total",
"Early access a novas features",
"Cursos exclusivos QuantScan"
]}
/>
</section> </section>
{/* 3. BENEFÍCIOS PRINCIPAIS */} {/* 3. BENEFÍCIOS PRINCIPAIS */}
@@ -319,22 +337,24 @@ export const PlansView: React.FC<PlansViewProps> = ({ isUnauthenticated, onGetSt
<th className="p-4 border-b border-white/5 text-center">Begin</th> <th className="p-4 border-b border-white/5 text-center">Begin</th>
<th className="p-4 border-b border-white/5 text-center text-brand-red">Pro</th> <th className="p-4 border-b border-white/5 text-center text-brand-red">Pro</th>
<th className="p-4 border-b border-white/5 text-center">Elite</th> <th className="p-4 border-b border-white/5 text-center">Elite</th>
<th className="p-4 border-b border-white/5 text-center">Lifetime</th>
</tr> </tr>
</thead> </thead>
<tbody className="text-xs font-medium text-zinc-300"> <tbody className="text-xs font-medium text-zinc-300">
{[ {[
{ label: "Nº de Análises", free: "8 / dia", pro: "15 / dia", elite: "Ilimitado" }, { label: "Nº de Análises", free: "8 / dia", pro: "15 / dia", elite: "30 / dia", lifetime: "Ilimitado" },
{ label: "Score de Probabilidade", free: "Básico", pro: "Avançado", elite: "Avançado+" }, { label: "Score de Probabilidade", free: "Básico", pro: "Avançado", elite: "Avançado+", lifetime: "Avançado+" },
{ label: "IA Adaptativa", free: "Não", pro: "Sim", elite: "Sim" }, { label: "IA Adaptativa", free: "Não", pro: "Sim", elite: "Sim", lifetime: "Sim" },
{ label: "Histórico de Sinais", free: "Limitado", pro: "Completo", elite: "Completo" }, { label: "Histórico de Sinais", free: "Limitado", pro: "Completo", elite: "Completo", lifetime: "Completo" },
{ label: "Suporte", free: "Comunidade", pro: "Prioritário", elite: "VIP Individual" }, { label: "Suporte", free: "Comunidade", pro: "Prioritário", elite: "VIP Individual", lifetime: "Premium 1-on-1" },
{ label: "Processamento", free: "Normal", pro: "Rápido", elite: "Ultra Prioridade" }, { label: "Processamento", free: "Normal", pro: "Rápido", elite: "Ultra Prioridade", lifetime: "Ultra Prioridade" },
].map((row, idx) => ( ].map((row, idx) => (
<tr key={idx} className="border-b border-white/5 hover:bg-white/5 transition-colors"> <tr key={idx} className="border-b border-white/5 hover:bg-white/5 transition-colors">
<td className="p-4 font-bold text-white">{row.label}</td> <td className="p-4 font-bold text-white">{row.label}</td>
<td className="p-4 text-center text-zinc-500">{row.free}</td> <td className="p-4 text-center text-zinc-500">{row.free}</td>
<td className="p-4 text-center font-bold text-white">{row.pro}</td> <td className="p-4 text-center font-bold text-white">{row.pro}</td>
<td className="p-4 text-center text-zinc-300">{row.elite}</td> <td className="p-4 text-center text-zinc-300">{row.elite}</td>
<td className="p-4 text-center text-brand-red font-bold">{row.lifetime}</td>
</tr> </tr>
))} ))}
</tbody> </tbody>
+2 -9
View File
@@ -17,14 +17,8 @@ export const SignalHistory: React.FC = () => {
where('userId', '==', auth.currentUser.uid) where('userId', '==', auth.currentUser.uid)
); );
// Force refresh every hour // The onSnapshot listener below automatically handles updates
const refreshInterval = setInterval(() => { // when data in Firestore changes.
// Small trick to trigger a re-subscribe if necessary,
// though onSnapshot should technically handle it.
// This satisfies the explicit request to refresh hourly.
console.log('Refreshing signal history...');
}, 60 * 60 * 1000);
const unsubscribe = onSnapshot(q, (snapshot) => { const unsubscribe = onSnapshot(q, (snapshot) => {
let newSignals: Signal[] = []; let newSignals: Signal[] = [];
snapshot.forEach((doc) => { snapshot.forEach((doc) => {
@@ -39,7 +33,6 @@ export const SignalHistory: React.FC = () => {
}); });
return () => { return () => {
clearInterval(refreshInterval);
unsubscribe(); unsubscribe();
}; };
}, []); }, []);
+48 -43
View File
@@ -3,26 +3,47 @@ import { AnalysisResponse, SignalType } from "../types";
const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY || '' }); const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY || '' });
export const analyzeForexChart = async (imageBase64: string, userNotes?: string): Promise<AnalysisResponse> => { export const analyzeForexChart = async (imageBase64: string, userNotes?: string, preferredMode?: 'Técnico' | 'Fundamental' | 'Híbrido'): Promise<AnalysisResponse> => {
const model = "gemini-3-flash-preview"; const model = "gemini-3-flash-preview";
const systemInstruction = ` const systemInstruction = `
Você é um Analista Institucional de Forex com IA adaptativa, especializado em Fluxo de Ordens, Liquidez e Comportamento Institucional (PRO Logic). Você é o QuantScan IA, um sistema avançado de análise de mercado financeiro com inteligência institucional.
Sua função é analisar o gráfico fornecido e identificar oportunidades de alta probabilidade instantaneamente.
Analise a imagem detalhadamente em busca de: Especializações: Smart Money Concepts (SMC), Liquidez, Momentum, Análise Fundamental macro, Aprendizado contínuo.
1. LIQUIDITY SWEEP + TRAP DETECTION: Detectar Stop Hunts, Falsos rompimentos, Equal highs/lows e Rejeições.
2. MOMENTUM + ENTRY TIMING: Avaliar força dos candles e rejeições.
3. ZONAS IMPORTANTES: Oferta/Demanda e Níveis psicológicos.
Detecte automaticamente o Timeframe e o Par de Moedas. Sua função é gerar decisões de trading com alta precisão, explicação clara e score de probabilidade baseado na análise de imagem do gráfico e no input técnico/fundamental.
Seja breve, preciso e ultrarrápido na resposta. MODOS DE ANÁLISE:
- Técnico: Foco em Timing, Estrutura e Execução.
- Fundamental: Foco em Macro, Notícias, Força de moedas.
- Híbrido: Combinação de Técnico e Fundamental (Recomendado).
DETECÇÃO AUTOMÁTICA: Timeframe (M1-D1), Par de moeda, Estrutura.
FORMATO DE SAÍDA (Obrigatório em JSON):
{
"mode": "Técnico" | "Fundamental" | "Híbrido",
"analiseGeral": "string",
"timeframe": "string",
"estrutura": "string",
"tecnica": "string", // Detalhada (SMC+Liquidez+Momentum)
"fundamental": "string", // Resumo macro
"decision": "BUY" | "SELL" | "WAIT",
"entry": "string",
"stopLoss": "string",
"takeProfit": "string",
"score": number, // 0-100
"justification": "string",
"alerta": "string",
"pair": "string"
}
`; `;
const prompt = ` const prompt = `
Analise este gráfico de Forex sob a óptica institucional. ${userNotes ? `Notas do usuário: ${userNotes}` : ''} Analise este gráfico sob a óptica do QuantScan IA.
Retorne a análise seguindo estritamente o esquema JSON fornecido. ${preferredMode ? `Use estritamente o modo de análise: ${preferredMode}.` : 'Detecte o melhor modo automaticamente.'}
${userNotes ? `Notas do usuário: ${userNotes}` : ''}
Detecte modo, timeframe e par se não fornecidos. Retorne JSON estrito.
`; `;
const response = await ai.models.generateContent({ const response = await ai.models.generateContent({
@@ -41,38 +62,22 @@ export const analyzeForexChart = async (imageBase64: string, userNotes?: string)
responseSchema: { responseSchema: {
type: Type.OBJECT, type: Type.OBJECT,
properties: { properties: {
pair: { type: Type.STRING, description: "Par de moedas detectado (ex: EURUSD)" }, mode: { type: Type.STRING, enum: ['Técnico', 'Fundamental', 'Híbrido'] },
timeframe: { type: Type.STRING, description: "Timeframe detectado (ex: H1)" }, analiseGeral: { type: Type.STRING },
structure: { type: Type.STRING, description: "Resumo da estrutura de mercado" }, pair: { type: Type.STRING },
conceptsDetected: { timeframe: { type: Type.STRING },
type: Type.ARRAY, estrutura: { type: Type.STRING },
items: { type: Type.STRING }, tecnica: { type: Type.STRING },
description: "Lista de conceitos identificados" fundamental: { type: Type.STRING },
}, decision: { type: Type.STRING, enum: ["BUY", "SELL", "WAIT"] },
decision: { entry: { type: Type.STRING },
type: Type.STRING, stopLoss: { type: Type.STRING },
enum: ["BUY", "SELL", "WAIT"], takeProfit: { type: Type.STRING },
description: "Decisão final de trading" score: { type: Type.NUMBER },
}, justification: { type: Type.STRING },
entry: { type: Type.STRING, description: "Preço ou zona de entrada" }, alerta: { type: Type.STRING }
stopLoss: { type: Type.STRING, description: "Preço de Stop Loss" },
takeProfit: { type: Type.STRING, description: "Preço de Take Profit" },
score: { type: Type.NUMBER, description: "Score de probabilidade de 0 a 100" },
justification: { type: Type.STRING, description: "Explicação técnica para o score" },
liquiditySweep: { type: Type.STRING, description: "Descrição do sweep de liquidez detectado ou armadilhas" },
momentum: { type: Type.STRING, description: "Análise do momentum e timing de entrada" },
keyZones: {
type: Type.ARRAY,
items: { type: Type.STRING },
description: "Zonas importantes detectadas"
},
institutionalContext: {
type: Type.STRING,
enum: ["Accumulation", "Manipulation", "Distribution", "None"],
description: "Fase do ciclo institucional"
}
}, },
required: ["pair", "timeframe", "structure", "conceptsDetected", "decision", "entry", "stopLoss", "takeProfit", "score", "justification", "liquiditySweep", "momentum", "keyZones", "institutionalContext"] required: ["mode", "analiseGeral", "pair", "timeframe", "estrutura", "tecnica", "fundamental", "decision", "entry", "stopLoss", "takeProfit", "score", "justification", "alerta"]
} }
} }
}); });
+15 -9
View File
@@ -18,8 +18,6 @@ export enum SignalResult {
export interface Signal { export interface Signal {
id: string; id: string;
pair: string;
timeframe: string;
timestamp: number; timestamp: number;
type: SignalType; type: SignalType;
entry: string; entry: string;
@@ -27,10 +25,18 @@ export interface Signal {
takeProfit: string; takeProfit: string;
score: number; score: number;
justification: string; justification: string;
logicElements: string[];
result: SignalResult; result: SignalResult;
screenshotUrl?: string; screenshotUrl?: string;
userId: string; userId: string;
// QuantScan IA fields
mode: string;
pair: string;
analiseGeral: string;
timeframe: string;
estrutura: string;
tecnica: string;
fundamental: string;
alerta: string;
} }
export interface UserStats { export interface UserStats {
@@ -45,18 +51,18 @@ export interface UserStats {
} }
export interface AnalysisResponse { export interface AnalysisResponse {
mode: 'Técnico' | 'Fundamental' | 'Híbrido';
analiseGeral: string;
pair: string; pair: string;
timeframe: string; timeframe: string;
structure: string; estrutura: string;
conceptsDetected: string[]; tecnica: string;
fundamental: string;
decision: SignalType; decision: SignalType;
entry: string; entry: string;
stopLoss: string; stopLoss: string;
takeProfit: string; takeProfit: string;
score: number; score: number;
justification: string; justification: string;
liquiditySweep?: string; alerta: string;
momentum?: string;
keyZones?: string[];
institutionalContext?: 'Accumulation' | 'Manipulation' | 'Distribution' | 'None';
} }