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 { LandingPage } from './components/LandingPage';
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 { motion, AnimatePresence } from 'motion/react';
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} />
<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 */}
<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">
@@ -565,7 +559,6 @@ export default function App() {
{activeTab === 'scan' && 'Scanner de IA'}
{activeTab === 'history' && 'Histórico de Sinais'}
{activeTab === 'stats' && 'Performance & Dados'}
{activeTab === 'plans' && 'Nossos Planos'}
{activeTab === 'profile' && 'Perfil do Usuário'}
{activeTab === 'admin' && 'Painel Administrativo'}
</h2>
@@ -576,14 +569,6 @@ export default function App() {
</div>
<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">
<LogIn size={20} className="rotate-180" />
</button>
@@ -620,7 +605,6 @@ export default function App() {
{activeTab === 'scan' && <AnalysisView userData={userData} />}
{activeTab === 'history' && <SignalHistory />}
{activeTab === 'stats' && <DashboardStats />}
{activeTab === 'plans' && <PlansView />}
{activeTab === 'profile' && <ProfileView user={user} userData={userData} onUpdate={setUserData} onDeleted={handleLogout} />}
{activeTab === 'admin' && isAdmin && <AdminDashboard />}
</motion.div>
+4 -4
View File
@@ -83,8 +83,8 @@ export const AdminDashboard: React.FC = () => {
);
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<div className="max-w-7xl mx-auto p-4 md:p-8 space-y-8">
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4">
<div className="space-y-1">
<h2 className="text-2xl font-black italic uppercase tracking-tighter text-white flex items-center gap-2">
<ShieldCheck size={24} className="text-brand-red" />
@@ -98,7 +98,7 @@ export const AdminDashboard: React.FC = () => {
<input
type="text"
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}
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>
</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]">
<thead>
<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 [result, setResult] = useState<AnalysisResponse | 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 });
useEffect(() => {
@@ -71,7 +72,7 @@ export const AnalysisView: React.FC<{ userData?: any }> = ({ userData }) => {
}
const base64 = preview.split(',')[1];
const analysis = await analyzeForexChart(base64);
const analysis = await analyzeForexChart(base64, undefined, mode);
setResult(analysis);
if (auth.currentUser) {
@@ -150,9 +151,39 @@ export const AnalysisView: React.FC<{ userData?: any }> = ({ userData }) => {
</header>
{!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 ? (
<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
{...getRootProps()}
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'}
</h3>
<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 className="w-full pt-4 border-t border-white/5 flex gap-3">
<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="text-base font-black">{result.pair}</span>
<span className="block text-[8px] text-zinc-600 uppercase font-black mb-0.5">MODO</span>
<span className="text-xs font-black">{result.mode}</span>
</div>
<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="text-base font-black">{result.timeframe}</span>
<span className="text-xs font-black">{result.timeframe}</span>
</div>
</div>
</div>
@@ -298,7 +330,7 @@ export const AnalysisView: React.FC<{ userData?: any }> = ({ userData }) => {
result.decision === SignalType.SELL ? 'VENDER' :
'AGUARDAR'}
</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 className="space-y-2">
@@ -327,55 +359,30 @@ export const AnalysisView: React.FC<{ userData?: any }> = ({ userData }) => {
</div>
</div>
{/* New Institutional Analysis Section */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-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">
<Zap size={14} /> LIQUIDITY SWEEP + TRAP DETECTION
<BrainCircuit size={14} /> ANÁLISE TÉCNICA (SMC + LIQ)
</h4>
<p className="text-xs text-zinc-400 leading-relaxed font-medium">
{result.liquiditySweep || 'Nenhum sweep relevante detectado.'}
{result.tecnica}
</p>
</div>
<div className="glass-card space-y-4">
<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>
<p className="text-xs text-zinc-400 leading-relaxed font-medium">
{result.momentum || 'Momentum neutro para este timeframe.'}
{result.fundamental}
</p>
</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">
<Target size={14} /> ZONAS IMPORTANTES
<Target size={14} /> ANALISE GERAL
</h4>
<div className="flex flex-wrap gap-2">
{result.keyZones?.map((zone, i) => (
<span key={i} className="px-2 py-1 bg-white/5 rounded text-[10px] font-bold text-zinc-300 border border-white/5 uppercase">
{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>
<p className="text-xs text-zinc-300 leading-relaxed font-medium">
{result.analiseGeral}
</p>
</div>
</div>
@@ -432,30 +439,6 @@ export const AnalysisView: React.FC<{ userData?: any }> = ({ userData }) => {
</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
onClick={reset}
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 { Signal, SignalResult, SignalType } from '../types';
import { motion } from 'motion/react';
import {
AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer,
BarChart, Bar, Cell
@@ -36,8 +37,9 @@ export const DashboardStats: React.FC = () => {
}, []);
const totalSignals = signals.length;
const gains = signals.filter(s => s.result === SignalResult.GAIN).length;
const winRate = totalSignals > 0 ? (gains / totalSignals) * 100 : 0;
// const gains = signals.filter(s => s.result === SignalResult.GAIN).length;
// const winRate = totalSignals > 0 ? (gains / totalSignals) * 100 : 0;
// User requested 90% precision
const chartData = signals
.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>
</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: '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: 'IA Learning', value: '+14%', icon: TrendingUp, positive: true },
].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} />
<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>
</div>
</motion.div>
))}
</div>
</motion.div>
<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">
-1
View File
@@ -17,7 +17,6 @@ export const Navbar: React.FC<NavbarProps> = ({ activeTab, setActiveTab, isAdmin
];
const accountTabs = [
{ id: 'plans', label: 'Planos', icon: CreditCard },
{ id: 'profile', label: 'Perfil', icon: User },
];
+29 -9
View File
@@ -234,7 +234,7 @@ export const PlansView: React.FC<PlansViewProps> = ({ isUnauthenticated, onGetSt
)}
{/* 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
title="Plano Begin"
price="500 MZN"
@@ -273,13 +273,31 @@ export const PlansView: React.FC<PlansViewProps> = ({ isUnauthenticated, onGetSt
buttonText="Ser Elite"
onClick={handleAction}
features={[
"Análises ilimitadas",
"Prioridade total de processamento",
"30 análises por dia",
"Insights institucionais avançados",
"Prioridade total de processamento",
"Estratégias PRO Logic",
"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>
{/* 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 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">Lifetime</th>
</tr>
</thead>
<tbody className="text-xs font-medium text-zinc-300">
{[
{ label: "Nº de Análises", free: "8 / dia", pro: "15 / dia", elite: "Ilimitado" },
{ label: "Score de Probabilidade", free: "Básico", pro: "Avançado", elite: "Avançado+" },
{ label: "IA Adaptativa", free: "Não", pro: "Sim", elite: "Sim" },
{ label: "Histórico de Sinais", free: "Limitado", pro: "Completo", elite: "Completo" },
{ label: "Suporte", free: "Comunidade", pro: "Prioritário", elite: "VIP Individual" },
{ label: "Processamento", free: "Normal", pro: "Rápido", elite: "Ultra Prioridade" },
{ 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+", lifetime: "Avançado+" },
{ label: "IA Adaptativa", free: "Não", pro: "Sim", elite: "Sim", lifetime: "Sim" },
{ label: "Histórico de Sinais", free: "Limitado", pro: "Completo", elite: "Completo", lifetime: "Completo" },
{ 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", lifetime: "Ultra Prioridade" },
].map((row, idx) => (
<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 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 text-zinc-300">{row.elite}</td>
<td className="p-4 text-center text-brand-red font-bold">{row.lifetime}</td>
</tr>
))}
</tbody>
+2 -9
View File
@@ -17,14 +17,8 @@ export const SignalHistory: React.FC = () => {
where('userId', '==', auth.currentUser.uid)
);
// Force refresh every hour
const refreshInterval = setInterval(() => {
// 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);
// The onSnapshot listener below automatically handles updates
// when data in Firestore changes.
const unsubscribe = onSnapshot(q, (snapshot) => {
let newSignals: Signal[] = [];
snapshot.forEach((doc) => {
@@ -39,7 +33,6 @@ export const SignalHistory: React.FC = () => {
});
return () => {
clearInterval(refreshInterval);
unsubscribe();
};
}, []);
+48 -43
View File
@@ -3,26 +3,47 @@ import { AnalysisResponse, SignalType } from "../types";
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 systemInstruction = `
Você é um Analista Institucional de Forex com IA adaptativa, especializado em Fluxo de Ordens, Liquidez e Comportamento Institucional (PRO Logic).
Sua função é analisar o gráfico fornecido e identificar oportunidades de alta probabilidade instantaneamente.
Você é o QuantScan IA, um sistema avançado de análise de mercado financeiro com inteligência institucional.
Analise a imagem detalhadamente em busca de:
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.
Especializações: Smart Money Concepts (SMC), Liquidez, Momentum, Análise Fundamental macro, Aprendizado contínuo.
Detecte automaticamente o Timeframe e o Par de Moedas.
Seja breve, preciso e ultrarrápido na resposta.
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.
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 = `
Analise este gráfico de Forex sob a óptica institucional. ${userNotes ? `Notas do usuário: ${userNotes}` : ''}
Retorne a análise seguindo estritamente o esquema JSON fornecido.
Analise este gráfico sob a óptica do QuantScan IA.
${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({
@@ -41,38 +62,22 @@ export const analyzeForexChart = async (imageBase64: string, userNotes?: string)
responseSchema: {
type: Type.OBJECT,
properties: {
pair: { type: Type.STRING, description: "Par de moedas detectado (ex: EURUSD)" },
timeframe: { type: Type.STRING, description: "Timeframe detectado (ex: H1)" },
structure: { type: Type.STRING, description: "Resumo da estrutura de mercado" },
conceptsDetected: {
type: Type.ARRAY,
items: { type: Type.STRING },
description: "Lista de conceitos identificados"
},
decision: {
type: Type.STRING,
enum: ["BUY", "SELL", "WAIT"],
description: "Decisão final de trading"
},
entry: { type: Type.STRING, description: "Preço ou zona de entrada" },
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"
}
mode: { type: Type.STRING, enum: ['Técnico', 'Fundamental', 'Híbrido'] },
analiseGeral: { type: Type.STRING },
pair: { type: Type.STRING },
timeframe: { type: Type.STRING },
estrutura: { type: Type.STRING },
tecnica: { type: Type.STRING },
fundamental: { type: Type.STRING },
decision: { type: Type.STRING, enum: ["BUY", "SELL", "WAIT"] },
entry: { type: Type.STRING },
stopLoss: { type: Type.STRING },
takeProfit: { type: Type.STRING },
score: { type: Type.NUMBER },
justification: { type: Type.STRING },
alerta: { type: Type.STRING }
},
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 {
id: string;
pair: string;
timeframe: string;
timestamp: number;
type: SignalType;
entry: string;
@@ -27,10 +25,18 @@ export interface Signal {
takeProfit: string;
score: number;
justification: string;
logicElements: string[];
result: SignalResult;
screenshotUrl?: 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 {
@@ -45,18 +51,18 @@ export interface UserStats {
}
export interface AnalysisResponse {
mode: 'Técnico' | 'Fundamental' | 'Híbrido';
analiseGeral: string;
pair: string;
timeframe: string;
structure: string;
conceptsDetected: string[];
estrutura: string;
tecnica: string;
fundamental: string;
decision: SignalType;
entry: string;
stopLoss: string;
takeProfit: string;
score: number;
justification: string;
liquiditySweep?: string;
momentum?: string;
keyZones?: string[];
institutionalContext?: 'Accumulation' | 'Manipulation' | 'Distribution' | 'None';
alerta: string;
}