torxyton commited on
Commit
51220c3
·
0 Parent(s):

Initial commit: Refatoração completa do sistema de análise de mercado com IA

Browse files
__pycache__/config.cpython-313.pyc ADDED
Binary file (6.21 kB). View file
 
__pycache__/market_analysis.cpython-313.pyc ADDED
Binary file (16.9 kB). View file
 
__pycache__/sentiment_analysis.cpython-313.pyc ADDED
Binary file (13.8 kB). View file
 
__pycache__/ui.cpython-313.pyc ADDED
Binary file (23.5 kB). View file
 
__pycache__/utils.cpython-313.pyc ADDED
Binary file (18.2 kB). View file
 
app.py ADDED
@@ -0,0 +1,580 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Aplicação de Análise de Mercado com IA para Scalping - Versão Refatorada."""
2
+
3
+ from typing import Dict, Any, Optional
4
+ import gradio as gr
5
+ from datetime import datetime
6
+
7
+ # Importar módulos refatorados
8
+ try:
9
+ from config import AppConfig
10
+ from market_analysis import TechnicalAnalysisEngine
11
+ from sentiment_analysis import SentimentAnalysisEngine
12
+ from ui import GradioInterface
13
+ from utils import LogUtils, ValidationUtils
14
+ except ImportError:
15
+ # Fallback para modo standalone se módulos não existirem
16
+ print("⚠️ Módulos refatorados não encontrados. Executando em modo standalone.")
17
+ AppConfig = None
18
+ TechnicalAnalysisEngine = None
19
+ SentimentAnalysisEngine = None
20
+ GradioInterface = None
21
+ LogUtils = None
22
+ ValidationUtils = None
23
+
24
+ # Engines de análise
25
+ technical_engine = None
26
+ sentiment_engine = None
27
+ model_info = {'available': False, 'description': 'IA Indisponível'}
28
+
29
+ # Inicializar engines
30
+ def initialize_engines():
31
+ """Inicializa engines de análise técnica e de sentimento."""
32
+ global technical_engine, sentiment_engine, model_info
33
+
34
+ try:
35
+ if TechnicalAnalysisEngine and SentimentAnalysisEngine:
36
+ # Inicializar engine de análise técnica
37
+ technical_engine = TechnicalAnalysisEngine()
38
+ if LogUtils:
39
+ LogUtils.log_analysis_result({'action': 'ENGINE_INIT', 'confidence': 100})
40
+
41
+ # Inicializar engine de análise de sentimento
42
+ sentiment_engine = SentimentAnalysisEngine()
43
+
44
+ # Obter informações do modelo de IA
45
+ model_info = sentiment_engine.get_model_info()
46
+ if LogUtils:
47
+ LogUtils.log_model_status(model_info)
48
+
49
+ print("✅ Engines inicializadas com sucesso")
50
+ else:
51
+ # Fallback para modo standalone
52
+ initialize_standalone_mode()
53
+
54
+ except Exception as e:
55
+ print(f"Erro na inicialização: {str(e)}")
56
+ # Fallback para modo básico
57
+ initialize_standalone_mode()
58
+
59
+ def initialize_standalone_mode():
60
+ """Inicializa modo standalone com funcionalidades básicas."""
61
+ global model_info
62
+
63
+ # Importações opcionais para IA
64
+ try:
65
+ from transformers import AutoTokenizer, AutoModelForSequenceClassification, pipeline
66
+ import torch
67
+ TRANSFORMERS_AVAILABLE = True
68
+ except ImportError:
69
+ TRANSFORMERS_AVAILABLE = False
70
+ print("⚠️ Transformers não disponível. Executando sem IA.")
71
+
72
+ # Lista de modelos alternativos (do mais específico para o mais geral)
73
+ FINANCIAL_MODELS = [
74
+ {
75
+ "name": "ProsusAI/finbert",
76
+ "description": "FinBERT - Modelo especializado em sentimento financeiro",
77
+ "labels": {"LABEL_0": "NEGATIVO", "LABEL_1": "NEUTRO", "LABEL_2": "POSITIVO"}
78
+ },
79
+ {
80
+ "name": "mrm8488/distilroberta-finetuned-financial-news-sentiment-analysis",
81
+ "description": "DistilRoBERTa - Modelo leve para notícias financeiras",
82
+ "labels": {"LABEL_0": "NEGATIVO", "LABEL_1": "NEUTRO", "LABEL_2": "POSITIVO"}
83
+ },
84
+ {
85
+ "name": "soleimanian/financial-roberta-large-sentiment",
86
+ "description": "Financial RoBERTa - Modelo robusto para textos financeiros",
87
+ "labels": {"LABEL_0": "NEGATIVO", "LABEL_1": "NEUTRO", "LABEL_2": "POSITIVO"}
88
+ },
89
+ {
90
+ "name": "cardiffnlp/twitter-roberta-base-sentiment-latest",
91
+ "description": "RoBERTa - Modelo geral de sentimento (fallback)",
92
+ "labels": {"LABEL_0": "NEGATIVO", "LABEL_1": "NEUTRO", "LABEL_2": "POSITIVO"}
93
+ }
94
+ ]
95
+
96
+ # Inicializar modelo de sentimento
97
+ sentiment_pipeline = None
98
+ current_model_info = None
99
+
100
+ if TRANSFORMERS_AVAILABLE:
101
+ for model_config in FINANCIAL_MODELS:
102
+ try:
103
+ print(f"🔄 Tentando carregar: {model_config['description']}")
104
+ sentiment_pipeline = pipeline(
105
+ "sentiment-analysis",
106
+ model=model_config["name"],
107
+ return_all_scores=True
108
+ )
109
+ current_model_info = model_config
110
+ print(f"✅ {model_config['description']} carregado com sucesso!")
111
+ break
112
+ except Exception as e:
113
+ print(f"❌ Falha ao carregar {model_config['name']}: {e}")
114
+ continue
115
+
116
+ if sentiment_pipeline is None:
117
+ print("❌ Nenhum modelo de sentimento pôde ser carregado.")
118
+ TRANSFORMERS_AVAILABLE = False
119
+ else:
120
+ print("⚠️ Transformers não disponível. Executando sem análise de sentimento IA.")
121
+
122
+ # Atualizar model_info global
123
+ if current_model_info:
124
+ model_info = {'available': True, 'description': current_model_info['description']}
125
+ else:
126
+ model_info = {'available': False, 'description': 'IA Indisponível'}
127
+
128
+ def parse_market_data(text):
129
+ """Extrai dados de mercado do texto de entrada usando engines ou fallback."""
130
+ if technical_engine and hasattr(technical_engine, 'parse_market_data'):
131
+ return technical_engine.parse_market_data(text)
132
+
133
+ # Fallback para modo standalone
134
+ import re
135
+ try:
136
+ # Regex patterns para extrair dados
137
+ price_match = re.search(r'P:([0-9,]+\.?\d*)', text)
138
+ variation_match = re.search(r'\(([\+\-]\d+\.?\d*%?)\)', text)
139
+ rsi_match = re.search(r'RSI:(\d+)', text)
140
+ ema_match = re.search(r'EMA:(ALTA|BAIXA)', text)
141
+ bb_match = re.search(r'BB:(DENTRO|SOBRE|ABAIXO|ACIMA)', text)
142
+ vol_match = re.search(r'Vol:([0-9\.]+)', text)
143
+
144
+ # Extrair valores
145
+ price = float(price_match.group(1).replace(',', '')) if price_match else 0
146
+ variation_str = variation_match.group(1) if variation_match else "0"
147
+ variation = float(variation_str.replace('%', '').replace('+', '')) if variation_str != "0" else 0
148
+ rsi = int(rsi_match.group(1)) if rsi_match else 50
149
+ ema_trend = ema_match.group(1) if ema_match else "NEUTRO"
150
+ bb_position = bb_match.group(1) if bb_match else "DENTRO"
151
+ volume = float(vol_match.group(1)) if vol_match else 0
152
+
153
+ return {
154
+ 'price': price,
155
+ 'variation': variation,
156
+ 'rsi': rsi,
157
+ 'ema_trend': ema_trend,
158
+ 'bb_position': bb_position,
159
+ 'volume': volume
160
+ }
161
+ except Exception as e:
162
+ return None
163
+
164
+ def analyze_sentiment(text):
165
+ """Analisa o sentimento do texto usando engines ou fallback."""
166
+ if sentiment_engine and hasattr(sentiment_engine, 'analyze_sentiment'):
167
+ return sentiment_engine.analyze_sentiment(text)
168
+
169
+ # Fallback para modo standalone
170
+ import re
171
+
172
+ # Verificar se temos pipeline carregado no modo standalone
173
+ if 'sentiment_pipeline' in globals() and sentiment_pipeline is not None:
174
+ try:
175
+ # Limpar e preparar o texto para análise
176
+ clean_text = re.sub(r'[^\w\s\+\-\%\.]', ' ', text)
177
+ clean_text = clean_text[:512] # Limitar tamanho para o modelo
178
+
179
+ result = sentiment_pipeline(clean_text)
180
+
181
+ # Processar resultado baseado no modelo
182
+ if isinstance(result, list) and len(result) > 0:
183
+ # Se return_all_scores=True, pegar o resultado com maior score
184
+ if isinstance(result[0], list):
185
+ predictions = result[0]
186
+ best_prediction = max(predictions, key=lambda x: x['score'])
187
+ else:
188
+ best_prediction = result[0]
189
+
190
+ # Mapear label usando o mapeamento do modelo atual
191
+ label = best_prediction['label']
192
+ confidence = best_prediction['score']
193
+
194
+ # Usar mapeamento específico do modelo ou fallback genérico
195
+ if 'current_model_info' in globals() and current_model_info and label in current_model_info['labels']:
196
+ sentiment_label = current_model_info['labels'][label]
197
+ else:
198
+ # Fallback para mapeamento genérico
199
+ label_lower = label.lower()
200
+ if 'neg' in label_lower:
201
+ sentiment_label = 'NEGATIVO'
202
+ elif 'pos' in label_lower:
203
+ sentiment_label = 'POSITIVO'
204
+ else:
205
+ sentiment_label = 'NEUTRO'
206
+
207
+ return {
208
+ 'sentiment': label.lower(),
209
+ 'confidence': confidence,
210
+ 'label': sentiment_label
211
+ }
212
+
213
+ except Exception as e:
214
+ print(f"Erro na análise de sentimento: {e}")
215
+
216
+ # Fallback padrão
217
+ return {
218
+ 'sentiment': 'neutral',
219
+ 'confidence': 0.5,
220
+ 'label': 'NEUTRO'
221
+ }
222
+
223
+ def analyze_scalping_signals(market_data, original_text=""):
224
+ """Analisa sinais para scalping usando engines ou fallback."""
225
+ if technical_engine and hasattr(technical_engine, 'analyze_scalping_signals'):
226
+ return technical_engine.analyze_scalping_signals(market_data, original_text)
227
+
228
+ # Fallback para modo standalone
229
+ if not market_data:
230
+ return {
231
+ 'action': 'AGUARDAR',
232
+ 'confidence': 0,
233
+ 'signals': [],
234
+ 'market_data': {},
235
+ 'sentiment': {'label': 'NEUTRO', 'confidence': 0.5}
236
+ }
237
+
238
+ # Análise de sentimento com FinBERT
239
+ sentiment_analysis = analyze_sentiment(original_text)
240
+
241
+ price = market_data['price']
242
+ variation = market_data['variation']
243
+ rsi = market_data['rsi']
244
+ ema_trend = market_data['ema_trend']
245
+ bb_position = market_data['bb_position']
246
+ volume = market_data['volume']
247
+
248
+ # Pontuação de confiança
249
+ confidence_score = 0
250
+ signals = []
251
+ action = 'AGUARDAR'
252
+
253
+ # === ANÁLISE RSI ===
254
+ if rsi <= 30: # Oversold
255
+ signals.append("RSI em zona de sobrevenda ({}): COMPRA".format(rsi))
256
+ confidence_score += 25
257
+ if action == 'AGUARDAR':
258
+ action = 'COMPRAR'
259
+ elif rsi >= 70: # Overbought
260
+ signals.append("RSI em zona de sobrecompra ({}): VENDA".format(rsi))
261
+ confidence_score += 25
262
+ if action == 'AGUARDAR':
263
+ action = 'VENDER'
264
+ elif 45 <= rsi <= 55:
265
+ signals.append("RSI neutro ({}): aguardar confirmação".format(rsi))
266
+
267
+ # === ANÁLISE EMA ===
268
+ if ema_trend == 'ALTA':
269
+ signals.append("Tendência EMA ALTA: viés de COMPRA")
270
+ confidence_score += 15
271
+ if action == 'VENDER':
272
+ confidence_score -= 10 # Conflito
273
+ elif action == 'AGUARDAR':
274
+ action = 'COMPRAR'
275
+ elif ema_trend == 'BAIXA':
276
+ signals.append("Tendência EMA BAIXA: viés de VENDA")
277
+ confidence_score += 15
278
+ if action == 'COMPRAR':
279
+ confidence_score -= 10 # Conflito
280
+ elif action == 'AGUARDAR':
281
+ action = 'VENDER'
282
+
283
+ # === ANÁLISE BOLLINGER BANDS ===
284
+ if bb_position == 'ABAIXO':
285
+ signals.append("Preço abaixo da banda inferior: COMPRA (reversão)")
286
+ confidence_score += 20
287
+ if action == 'AGUARDAR':
288
+ action = 'COMPRAR'
289
+ elif bb_position == 'ACIMA' or bb_position == 'SOBRE':
290
+ signals.append("Preço acima da banda superior: VENDA (reversão)")
291
+ confidence_score += 20
292
+ if action == 'AGUARDAR':
293
+ action = 'VENDER'
294
+ elif bb_position == 'DENTRO':
295
+ signals.append("Preço dentro das bandas: aguardar breakout")
296
+ confidence_score += 5
297
+
298
+ # === ANÁLISE DE MOMENTUM (Variação) ===
299
+ if abs(variation) >= 0.05: # Movimento significativo
300
+ if variation > 0:
301
+ signals.append("Momentum positivo (+{:.2f}%): seguir tendência".format(variation))
302
+ confidence_score += 10
303
+ if action == 'VENDER':
304
+ confidence_score -= 5
305
+ else:
306
+ signals.append("Momentum negativo ({:.2f}%): seguir tendência".format(variation))
307
+ confidence_score += 10
308
+ if action == 'COMPRAR':
309
+ confidence_score -= 5
310
+
311
+ # === ANÁLISE DE VOLUME ===
312
+ if volume > 1.0: # Volume alto
313
+ signals.append("Volume alto ({:.1f}x): confirma movimento".format(volume))
314
+ confidence_score += 10
315
+ elif volume < 0.5: # Volume baixo
316
+ signals.append("Volume baixo ({:.1f}x): cuidado com falsos sinais".format(volume))
317
+ confidence_score -= 5
318
+
319
+ # === ANÁLISE DE SENTIMENTO IA (FinBERT) ===
320
+ sentiment_label = sentiment_analysis['label']
321
+ sentiment_conf = sentiment_analysis['confidence']
322
+
323
+ if sentiment_label == 'POSITIVO':
324
+ signals.append("🤖 IA Sentimento: POSITIVO ({:.1f}%): viés de COMPRA".format(sentiment_conf * 100))
325
+ confidence_score += int(sentiment_conf * 20) # Até 20 pontos
326
+ if action == 'AGUARDAR':
327
+ action = 'COMPRAR'
328
+ elif action == 'VENDER':
329
+ confidence_score -= 10 # Conflito com sentimento
330
+ elif sentiment_label == 'NEGATIVO':
331
+ signals.append("🤖 IA Sentimento: NEGATIVO ({:.1f}%): viés de VENDA".format(sentiment_conf * 100))
332
+ confidence_score += int(sentiment_conf * 20) # Até 20 pontos
333
+ if action == 'AGUARDAR':
334
+ action = 'VENDER'
335
+ elif action == 'COMPRAR':
336
+ confidence_score -= 10 # Conflito com sentimento
337
+ else:
338
+ signals.append("🤖 IA Sentimento: NEUTRO ({:.1f}%): sem viés claro".format(sentiment_conf * 100))
339
+
340
+ # === REGRAS ESPECÍFICAS DE SCALPING ===
341
+
342
+ # Scalping: RSI extremo + EMA contrária = reversão forte
343
+ if (rsi <= 25 and ema_trend == 'BAIXA') or (rsi >= 75 and ema_trend == 'ALTA'):
344
+ signals.append("🚨 SINAL FORTE: RSI extremo com EMA contrária - REVERSÃO")
345
+ confidence_score += 30
346
+
347
+ # Scalping: RSI + BB alinhados
348
+ if rsi <= 35 and bb_position == 'ABAIXO':
349
+ signals.append("🎯 SETUP PERFEITO: RSI baixo + BB abaixo - COMPRA FORTE")
350
+ confidence_score += 35
351
+ action = 'COMPRAR'
352
+ elif rsi >= 65 and (bb_position == 'ACIMA' or bb_position == 'SOBRE'):
353
+ signals.append("🎯 SETUP PERFEITO: RSI alto + BB acima - VENDA FORTE")
354
+ confidence_score += 35
355
+ action = 'VENDER'
356
+
357
+ # Limitar confiança
358
+ confidence_score = min(confidence_score, 95)
359
+ confidence_score = max(confidence_score, 10)
360
+
361
+ return {
362
+ 'action': action,
363
+ 'confidence': confidence_score,
364
+ 'signals': signals,
365
+ 'market_data': market_data,
366
+ 'sentiment': sentiment_analysis
367
+ }
368
+
369
+ def generate_trading_response(analysis):
370
+ """Gera resposta formatada para trading com análise de sentimento IA"""
371
+
372
+ action = analysis['action']
373
+ confidence = analysis['confidence']
374
+ signals = analysis.get('signals', [])
375
+ market_data = analysis.get('market_data', {})
376
+ sentiment = analysis.get('sentiment', {'label': 'NEUTRO', 'confidence': 0.5})
377
+
378
+ # Emojis e cores baseados na ação
379
+ if action == 'COMPRAR':
380
+ emoji = "🟢"
381
+ action_emoji = "📈"
382
+ color = "verde"
383
+ direction = "LONG"
384
+ elif action == 'VENDER':
385
+ emoji = "🔴"
386
+ action_emoji = "📉"
387
+ color = "vermelho"
388
+ direction = "SHORT"
389
+ else:
390
+ emoji = "🟡"
391
+ action_emoji = "⏸️"
392
+ color = "amarelo"
393
+ direction = "NEUTRO"
394
+
395
+ # Emojis para sentimento
396
+ sentiment_emojis = {
397
+ 'POSITIVO': '😊💚',
398
+ 'NEGATIVO': '😟💔',
399
+ 'NEUTRO': '😐💛'
400
+ }
401
+
402
+ # Barra de confiança
403
+ confidence_bars = "█" * int(confidence / 10) + "░" * (10 - int(confidence / 10))
404
+
405
+ # Classificação de confiança
406
+ if confidence >= 80:
407
+ conf_level = "MUITO ALTA"
408
+ elif confidence >= 65:
409
+ conf_level = "ALTA"
410
+ elif confidence >= 50:
411
+ conf_level = "MODERADA"
412
+ else:
413
+ conf_level = "BAIXA"
414
+
415
+ # Timestamp
416
+ timestamp = datetime.now().strftime("%H:%M:%S")
417
+
418
+ response = f"""{emoji} **ANÁLISE SCALPING - WIN M1/M5**
419
+
420
+ **⏰ Timestamp:** {timestamp}
421
+ **📊 Instrumento:** WINV25
422
+
423
+ **🎯 DECISÃO DE TRADING:**
424
+ • **Ação:** {action} {action_emoji}
425
+ • **Direção:** {direction}
426
+ • **Confiança:** {confidence:.0f}% ({conf_level})
427
+ • **Visual:** {confidence_bars}
428
+
429
+ **🤖 ANÁLISE DE SENTIMENTO IA (FinBERT):**
430
+ • **Sentimento:** {sentiment_emojis.get(sentiment['label'], '😐💛')} **{sentiment['label']}** ({sentiment['confidence']*100:.1f}%)
431
+
432
+ **📈 DADOS DE MERCADO:**
433
+ • **Preço:** {market_data.get('price', 'N/A'):,.0f}
434
+ • **Variação:** {market_data.get('variation', 0):+.2f}%
435
+ • **RSI:** {market_data.get('rsi', 'N/A')}
436
+ • **EMA:** {market_data.get('ema_trend', 'N/A')}
437
+ • **Bollinger:** {market_data.get('bb_position', 'N/A')}
438
+ • **Volume:** {market_data.get('volume', 0):.1f}x
439
+
440
+ **🔍 ANÁLISE TÉCNICA + IA:**"""
441
+
442
+ for i, signal in enumerate(signals[:5], 1): # Máximo 5 sinais
443
+ response += f"\n{i}. {signal}"
444
+
445
+ # Recomendações específicas
446
+ response += f"\n\n**⚡ RECOMENDAÇÕES SCALPING:**"
447
+
448
+ if action == 'COMPRAR':
449
+ response += f"""
450
+ • **Stop Loss:** -{market_data.get('price', 0) * 0.0007:.0f} pts (0.07%)
451
+ • **Take Profit:** +{market_data.get('price', 0) * 0.0015:.0f} pts (0.15%)
452
+ • **Timeframe:** M1/M5
453
+ • **Risk/Reward:** 1:2"""
454
+ elif action == 'VENDER':
455
+ response += f"""
456
+ • **Stop Loss:** +{market_data.get('price', 0) * 0.0007:.0f} pts (0.07%)
457
+ • **Take Profit:** -{market_data.get('price', 0) * 0.0015:.0f} pts (0.15%)
458
+ • **Timeframe:** M1/M5
459
+ • **Risk/Reward:** 1:2"""
460
+ else:
461
+ response += """
462
+ • **Aguardar:** Setup mais definido
463
+ • **Monitorar:** Rompimentos de suporte/resistência
464
+ • **Observar:** Confluência de sinais técnicos"""
465
+
466
+ if confidence < 60:
467
+ response += f"\n\n⚠️ **ATENÇÃO:** Confiança {conf_level} - Aguarde confirmação adicional!"
468
+
469
+ response += f"\n\n---\n*🤖 Análise Scalping Automatizada - {timestamp}*"
470
+
471
+ return response
472
+
473
+ def process_trading_analysis(text):
474
+ """Função principal que processa a análise de trading usando engines ou fallback."""
475
+
476
+ # Validação de entrada
477
+ if ValidationUtils and hasattr(ValidationUtils, 'validate_text_input'):
478
+ if not ValidationUtils.validate_text_input(text):
479
+ return "⚠️ **Erro:** Entrada de texto inválida ou muito curta."
480
+ else:
481
+ # Fallback validation
482
+ if not text or not text.strip():
483
+ return "⚠️ **Erro:** Nenhum dado de mercado fornecido para análise."
484
+
485
+ try:
486
+ # Parse dos dados de mercado
487
+ market_data = parse_market_data(text)
488
+
489
+ if not market_data:
490
+ return "⚠️ **Erro:** Não foi possível extrair dados válidos do texto fornecido."
491
+
492
+ # Análise dos sinais de scalping
493
+ analysis = analyze_scalping_signals(market_data, text)
494
+
495
+ # Log da análise se disponível
496
+ if LogUtils and hasattr(LogUtils, 'log_analysis_result'):
497
+ LogUtils.log_analysis_result(analysis)
498
+
499
+ # Gerar resposta formatada para a interface
500
+ return generate_trading_response(analysis)
501
+
502
+ except Exception as e:
503
+ error_msg = f"Erro durante a análise: {str(e)}"
504
+ if LogUtils and hasattr(LogUtils, 'log_error'):
505
+ LogUtils.log_error(error_msg, "PROCESS_ANALYSIS")
506
+
507
+ return f"❌ **Erro:** {error_msg}"
508
+
509
+ # Função principal de análise para a interface
510
+ def main_analysis_function(text: str) -> str:
511
+ """Função principal de análise que será usada pela interface."""
512
+ result = process_trading_analysis(text)
513
+
514
+ # Se o resultado é uma string (erro ou resposta formatada), retornar diretamente
515
+ if isinstance(result, str):
516
+ return result
517
+
518
+ # Se é um dicionário (análise estruturada), formatar para string
519
+ if isinstance(result, dict):
520
+ return generate_trading_response(result)
521
+
522
+ return "❌ **Erro:** Resultado de análise inválido."
523
+
524
+ # Criar e configurar interface
525
+ def create_interface():
526
+ """Cria interface usando a nova arquitetura modular."""
527
+ if GradioInterface:
528
+ # Usar interface refatorada
529
+ interface = GradioInterface(
530
+ analysis_function=main_analysis_function,
531
+ model_info=model_info
532
+ )
533
+ return interface.create_interface()
534
+ else:
535
+ # Fallback para interface simples
536
+ return create_fallback_interface()
537
+
538
+ def create_fallback_interface():
539
+ """Cria interface simples para modo standalone."""
540
+ example_data = AppConfig.EXAMPLE_INPUT if AppConfig else """IBOV: 129.847 (+0,85%)
541
+ RSI: 65
542
+ EMA 9: Tendência de alta
543
+ Bollinger: Preço próximo à banda superior
544
+ Volume: 2.3x da média
545
+ Notícias: Mercado otimista com dados do PIB"""
546
+
547
+ with gr.Blocks(title="📈 Análise de Mercado IA") as interface:
548
+ gr.HTML("""
549
+ <div style="text-align: center; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 10px; margin-bottom: 20px;">
550
+ <h1 style="color: white; margin: 0; font-size: 2.5em;">📈 Análise de Mercado IA</h1>
551
+ <p style="color: #f0f0f0; margin: 10px 0 0 0;">Sistema de análise técnica para scalping</p>
552
+ </div>
553
+ """)
554
+
555
+ with gr.Row():
556
+ with gr.Column(scale=2):
557
+ market_input = gr.Textbox(
558
+ label="Dados do Mercado",
559
+ placeholder=example_data,
560
+ lines=8
561
+ )
562
+ analyze_btn = gr.Button("🔍 Analisar", variant="primary")
563
+
564
+ with gr.Column(scale=3):
565
+ result_output = gr.HTML()
566
+
567
+ analyze_btn.click(
568
+ fn=main_analysis_function,
569
+ inputs=[market_input],
570
+ outputs=[result_output]
571
+ )
572
+
573
+ return interface
574
+
575
+ # Inicializar engines e criar interface
576
+ initialize_engines()
577
+ demo = create_interface()
578
+
579
+ if __name__ == "__main__":
580
+ demo.launch()
config.py ADDED
@@ -0,0 +1,209 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Configurações e constantes do sistema de análise de trading."""
2
+
3
+ from typing import Dict, List, Any
4
+
5
+ # Configurações de modelos de IA
6
+ FINANCIAL_MODELS = [
7
+ {
8
+ "name": "ProsusAI/finbert",
9
+ "description": "FinBERT - Modelo especializado em sentimento financeiro",
10
+ "labels": {"LABEL_0": "NEGATIVO", "LABEL_1": "NEUTRO", "LABEL_2": "POSITIVO"}
11
+ },
12
+ {
13
+ "name": "mrm8488/distilroberta-finetuned-financial-news-sentiment-analysis",
14
+ "description": "DistilRoBERTa - Modelo leve para notícias financeiras",
15
+ "labels": {"LABEL_0": "NEGATIVO", "LABEL_1": "NEUTRO", "LABEL_2": "POSITIVO"}
16
+ },
17
+ {
18
+ "name": "soleimanian/financial-roberta-large-sentiment",
19
+ "description": "Financial RoBERTa - Modelo robusto para textos financeiros",
20
+ "labels": {"LABEL_0": "NEGATIVO", "LABEL_1": "NEUTRO", "LABEL_2": "POSITIVO"}
21
+ },
22
+ {
23
+ "name": "cardiffnlp/twitter-roberta-base-sentiment-latest",
24
+ "description": "RoBERTa - Modelo geral de sentimento (fallback)",
25
+ "labels": {"LABEL_0": "NEGATIVO", "LABEL_1": "NEUTRO", "LABEL_2": "POSITIVO"}
26
+ }
27
+ ]
28
+
29
+ # Configurações de análise técnica
30
+ class TechnicalAnalysisConfig:
31
+ """Configurações para análise técnica."""
32
+
33
+ # RSI thresholds
34
+ RSI_OVERSOLD = 30
35
+ RSI_OVERBOUGHT = 70
36
+ RSI_EXTREME_OVERSOLD = 25
37
+ RSI_EXTREME_OVERBOUGHT = 75
38
+ RSI_NEUTRAL_MIN = 45
39
+ RSI_NEUTRAL_MAX = 55
40
+
41
+ # Bollinger Bands positions
42
+ BB_POSITIONS = {
43
+ 'ABAIXO': 'below',
44
+ 'ACIMA': 'above',
45
+ 'SOBRE': 'above',
46
+ 'DENTRO': 'inside'
47
+ }
48
+
49
+ # EMA trends
50
+ EMA_TRENDS = {
51
+ 'ALTA': 'up',
52
+ 'BAIXA': 'down',
53
+ 'NEUTRO': 'neutral'
54
+ }
55
+
56
+ # Volume thresholds
57
+ VOLUME_HIGH_THRESHOLD = 1.0
58
+ VOLUME_LOW_THRESHOLD = 0.5
59
+
60
+ # Momentum thresholds
61
+ SIGNIFICANT_MOVEMENT_THRESHOLD = 0.05 # 5%
62
+
63
+ # Configurações de scoring
64
+ class ScoringConfig:
65
+ """Configurações para sistema de pontuação."""
66
+
67
+ # Pontuações base
68
+ RSI_SCORE = 25
69
+ EMA_SCORE = 15
70
+ BB_SCORE = 20
71
+ MOMENTUM_SCORE = 10
72
+ VOLUME_SCORE = 10
73
+ SENTIMENT_MAX_SCORE = 20
74
+
75
+ # Bonificações especiais
76
+ PERFECT_SETUP_BONUS = 35
77
+ STRONG_REVERSAL_BONUS = 30
78
+
79
+ # Penalidades
80
+ CONFLICT_PENALTY = 10
81
+ LOW_VOLUME_PENALTY = 5
82
+
83
+ # Limites de confiança
84
+ MAX_CONFIDENCE = 95
85
+ MIN_CONFIDENCE = 10
86
+
87
+ # Configurações de trading
88
+ class TradingConfig:
89
+ """Configurações para recomendações de trading."""
90
+
91
+ # Risk management
92
+ STOP_LOSS_PERCENTAGE = 0.0007 # 0.07%
93
+ TAKE_PROFIT_PERCENTAGE = 0.0015 # 0.15%
94
+ RISK_REWARD_RATIO = 2 # 1:2
95
+
96
+ # Timeframes
97
+ SCALPING_TIMEFRAMES = ['M1', 'M5']
98
+
99
+ # Confidence levels
100
+ CONFIDENCE_LEVELS = {
101
+ 'MUITO_ALTA': 80,
102
+ 'ALTA': 65,
103
+ 'MODERADA': 50,
104
+ 'BAIXA': 0
105
+ }
106
+
107
+ # Configurações de interface
108
+ class UIConfig:
109
+ """Configurações para interface do usuário."""
110
+
111
+ # Emojis para ações
112
+ ACTION_EMOJIS = {
113
+ 'COMPRAR': {'main': '🟢', 'action': '📈'},
114
+ 'VENDER': {'main': '🔴', 'action': '📉'},
115
+ 'AGUARDAR': {'main': '🟡', 'action': '⏸️'}
116
+ }
117
+
118
+ # Emojis para sentimento
119
+ SENTIMENT_EMOJIS = {
120
+ 'POSITIVO': '😊💚',
121
+ 'NEGATIVO': '😟💔',
122
+ 'NEUTRO': '😐💛'
123
+ }
124
+
125
+ # Cores para ações
126
+ ACTION_COLORS = {
127
+ 'COMPRAR': 'verde',
128
+ 'VENDER': 'vermelho',
129
+ 'AGUARDAR': 'amarelo'
130
+ }
131
+
132
+ # Direções de trading
133
+ TRADING_DIRECTIONS = {
134
+ 'COMPRAR': 'LONG',
135
+ 'VENDER': 'SHORT',
136
+ 'AGUARDAR': 'NEUTRO'
137
+ }
138
+
139
+ # Configurações de regex para parsing
140
+ class RegexPatterns:
141
+ """Padrões regex para extração de dados."""
142
+
143
+ PRICE_PATTERN = r'P:([0-9,]+\.?\d*)'
144
+ VARIATION_PATTERN = r'\(([\+\-]\d+\.?\d*%?)\)'
145
+ RSI_PATTERN = r'RSI:(\d+)'
146
+ EMA_PATTERN = r'EMA:(ALTA|BAIXA)'
147
+ BB_PATTERN = r'BB:(DENTRO|SOBRE|ABAIXO|ACIMA)'
148
+ VOLUME_PATTERN = r'Vol:([0-9\.]+)'
149
+
150
+ # Configurações de IA
151
+ class AIConfig:
152
+ """Configurações para análise de IA."""
153
+
154
+ # Tamanho máximo de texto para análise
155
+ MAX_TEXT_LENGTH = 512
156
+
157
+ # Configurações do pipeline
158
+ PIPELINE_CONFIG = {
159
+ 'task': 'sentiment-analysis',
160
+ 'return_all_scores': True
161
+ }
162
+
163
+ # Mapeamento genérico de labels
164
+ GENERIC_LABEL_MAPPING = {
165
+ 'negative': 'NEGATIVO',
166
+ 'positive': 'POSITIVO',
167
+ 'neutral': 'NEUTRO'
168
+ }
169
+
170
+ # Configurações gerais da aplicação
171
+ class AppConfig:
172
+ """Configurações gerais da aplicação."""
173
+
174
+ # Informações da aplicação
175
+ APP_TITLE = "🧛‍♂️ VAMPIRE SCALPING BOT"
176
+ APP_SUBTITLE = "Análise Técnica + IA Financeira para WIN M1/M5"
177
+ APP_DESCRIPTION = "Sistema avançado de análise para scalping com FinBERT e indicadores técnicos"
178
+
179
+ # Configurações do servidor
180
+ DEFAULT_HOST = "127.0.0.1"
181
+ DEFAULT_PORT = 7860
182
+
183
+ # Configurações de logging
184
+ LOG_FORMAT = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
185
+
186
+ # Mensagens de status
187
+ STATUS_MESSAGES = {
188
+ 'AI_UNAVAILABLE': '⚠️ Transformers não disponível. Executando sem IA.',
189
+ 'AI_LOADING': '🔄 Tentando carregar: {}',
190
+ 'AI_SUCCESS': '✅ {} carregado com sucesso!',
191
+ 'AI_FAILED': '❌ Falha ao carregar {}: {}',
192
+ 'NO_MODEL_LOADED': '❌ Nenhum modelo de sentimento pôde ser carregado.'
193
+ }
194
+
195
+ # Configurações de exemplo de dados
196
+ class ExampleData:
197
+ """Dados de exemplo para testes e demonstração."""
198
+
199
+ SAMPLE_MARKET_DATA = {
200
+ 'bullish': 'P:134,500(+0.85%) | EMA:ALTA | RSI:35 | BB:ABAIXO | Vol:1.2',
201
+ 'bearish': 'P:133,200(-1.20%) | EMA:BAIXA | RSI:75 | BB:ACIMA | Vol:1.5',
202
+ 'neutral': 'P:133,850(+0.15%) | EMA:ALTA | RSI:52 | BB:DENTRO | Vol:0.8'
203
+ }
204
+
205
+ SAMPLE_NEWS_TEXT = {
206
+ 'positive': 'Mercado em alta com boas perspectivas econômicas',
207
+ 'negative': 'Preocupações com inflação afetam mercado',
208
+ 'neutral': 'Mercado aguarda dados econômicos'
209
+ }
market_analysis.py ADDED
@@ -0,0 +1,427 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Módulo de análise técnica de mercado para scalping."""
2
+
3
+ import re
4
+ from typing import Dict, List, Optional, Any
5
+ from dataclasses import dataclass
6
+ from datetime import datetime
7
+
8
+ from config import (
9
+ TechnicalAnalysisConfig,
10
+ ScoringConfig,
11
+ TradingConfig,
12
+ RegexPatterns
13
+ )
14
+
15
+
16
+ @dataclass
17
+ class MarketData:
18
+ """Classe para representar dados de mercado."""
19
+ price: float
20
+ variation: float
21
+ rsi: int
22
+ ema_trend: str
23
+ bb_position: str
24
+ volume: float
25
+
26
+ def __post_init__(self):
27
+ """Validação dos dados após inicialização."""
28
+ if self.price < 0:
29
+ raise ValueError("Preço não pode ser negativo")
30
+ if not 0 <= self.rsi <= 100:
31
+ raise ValueError("RSI deve estar entre 0 e 100")
32
+ if self.volume < 0:
33
+ raise ValueError("Volume não pode ser negativo")
34
+
35
+
36
+ @dataclass
37
+ class TechnicalSignal:
38
+ """Classe para representar um sinal técnico."""
39
+ indicator: str
40
+ signal_type: str # 'BUY', 'SELL', 'NEUTRAL'
41
+ strength: int # 0-100
42
+ description: str
43
+ confidence_impact: int
44
+
45
+
46
+ class MarketDataParser:
47
+ """Classe responsável por extrair dados de mercado do texto."""
48
+
49
+ @staticmethod
50
+ def parse_market_data(text: str) -> Optional[MarketData]:
51
+ """Extrai dados de mercado do texto de entrada."""
52
+ try:
53
+ # Extrair valores usando regex
54
+ price_match = re.search(RegexPatterns.PRICE_PATTERN, text)
55
+ variation_match = re.search(RegexPatterns.VARIATION_PATTERN, text)
56
+ rsi_match = re.search(RegexPatterns.RSI_PATTERN, text)
57
+ ema_match = re.search(RegexPatterns.EMA_PATTERN, text)
58
+ bb_match = re.search(RegexPatterns.BB_PATTERN, text)
59
+ vol_match = re.search(RegexPatterns.VOLUME_PATTERN, text)
60
+
61
+ # Processar valores extraídos
62
+ price = float(price_match.group(1).replace(',', '')) if price_match else 0
63
+
64
+ variation_str = variation_match.group(1) if variation_match else "0"
65
+ variation = float(variation_str.replace('%', '').replace('+', '')) if variation_str != "0" else 0
66
+
67
+ rsi = int(rsi_match.group(1)) if rsi_match else 50
68
+ ema_trend = ema_match.group(1) if ema_match else "NEUTRO"
69
+ bb_position = bb_match.group(1) if bb_match else "DENTRO"
70
+ volume = float(vol_match.group(1)) if vol_match else 0
71
+
72
+ return MarketData(
73
+ price=price,
74
+ variation=variation,
75
+ rsi=rsi,
76
+ ema_trend=ema_trend,
77
+ bb_position=bb_position,
78
+ volume=volume
79
+ )
80
+
81
+ except (ValueError, AttributeError) as e:
82
+ print(f"Erro ao processar dados de mercado: {e}")
83
+ return None
84
+
85
+
86
+ class RSIAnalyzer:
87
+ """Analisador de RSI."""
88
+
89
+ @staticmethod
90
+ def analyze(rsi: int) -> TechnicalSignal:
91
+ """Analisa o RSI e retorna sinal técnico."""
92
+ config = TechnicalAnalysisConfig()
93
+
94
+ if rsi <= config.RSI_OVERSOLD:
95
+ if rsi <= config.RSI_EXTREME_OVERSOLD:
96
+ return TechnicalSignal(
97
+ indicator="RSI",
98
+ signal_type="BUY",
99
+ strength=90,
100
+ description=f"RSI em zona de sobrevenda extrema ({rsi}): COMPRA FORTE",
101
+ confidence_impact=ScoringConfig.RSI_SCORE + 10
102
+ )
103
+ else:
104
+ return TechnicalSignal(
105
+ indicator="RSI",
106
+ signal_type="BUY",
107
+ strength=70,
108
+ description=f"RSI em zona de sobrevenda ({rsi}): COMPRA",
109
+ confidence_impact=ScoringConfig.RSI_SCORE
110
+ )
111
+
112
+ elif rsi >= config.RSI_OVERBOUGHT:
113
+ if rsi >= config.RSI_EXTREME_OVERBOUGHT:
114
+ return TechnicalSignal(
115
+ indicator="RSI",
116
+ signal_type="SELL",
117
+ strength=90,
118
+ description=f"RSI em zona de sobrecompra extrema ({rsi}): VENDA FORTE",
119
+ confidence_impact=ScoringConfig.RSI_SCORE + 10
120
+ )
121
+ else:
122
+ return TechnicalSignal(
123
+ indicator="RSI",
124
+ signal_type="SELL",
125
+ strength=70,
126
+ description=f"RSI em zona de sobrecompra ({rsi}): VENDA",
127
+ confidence_impact=ScoringConfig.RSI_SCORE
128
+ )
129
+
130
+ elif config.RSI_NEUTRAL_MIN <= rsi <= config.RSI_NEUTRAL_MAX:
131
+ return TechnicalSignal(
132
+ indicator="RSI",
133
+ signal_type="NEUTRAL",
134
+ strength=30,
135
+ description=f"RSI neutro ({rsi}): aguardar confirmação",
136
+ confidence_impact=0
137
+ )
138
+
139
+ else:
140
+ return TechnicalSignal(
141
+ indicator="RSI",
142
+ signal_type="NEUTRAL",
143
+ strength=50,
144
+ description=f"RSI em zona intermediária ({rsi})",
145
+ confidence_impact=0
146
+ )
147
+
148
+
149
+ class EMAAnalyzer:
150
+ """Analisador de EMA."""
151
+
152
+ @staticmethod
153
+ def analyze(ema_trend: str) -> TechnicalSignal:
154
+ """Analisa a tendência EMA e retorna sinal técnico."""
155
+ if ema_trend == 'ALTA':
156
+ return TechnicalSignal(
157
+ indicator="EMA",
158
+ signal_type="BUY",
159
+ strength=60,
160
+ description="Tendência EMA ALTA: viés de COMPRA",
161
+ confidence_impact=ScoringConfig.EMA_SCORE
162
+ )
163
+ elif ema_trend == 'BAIXA':
164
+ return TechnicalSignal(
165
+ indicator="EMA",
166
+ signal_type="SELL",
167
+ strength=60,
168
+ description="Tendência EMA BAIXA: viés de VENDA",
169
+ confidence_impact=ScoringConfig.EMA_SCORE
170
+ )
171
+ else:
172
+ return TechnicalSignal(
173
+ indicator="EMA",
174
+ signal_type="NEUTRAL",
175
+ strength=30,
176
+ description="Tendência EMA neutra",
177
+ confidence_impact=0
178
+ )
179
+
180
+
181
+ class BollingerBandsAnalyzer:
182
+ """Analisador de Bollinger Bands."""
183
+
184
+ @staticmethod
185
+ def analyze(bb_position: str) -> TechnicalSignal:
186
+ """Analisa a posição nas Bollinger Bands e retorna sinal técnico."""
187
+ if bb_position == 'ABAIXO':
188
+ return TechnicalSignal(
189
+ indicator="BB",
190
+ signal_type="BUY",
191
+ strength=80,
192
+ description="Preço abaixo da banda inferior: COMPRA (reversão)",
193
+ confidence_impact=ScoringConfig.BB_SCORE
194
+ )
195
+ elif bb_position in ['ACIMA', 'SOBRE']:
196
+ return TechnicalSignal(
197
+ indicator="BB",
198
+ signal_type="SELL",
199
+ strength=80,
200
+ description="Preço acima da banda superior: VENDA (reversão)",
201
+ confidence_impact=ScoringConfig.BB_SCORE
202
+ )
203
+ else: # DENTRO
204
+ return TechnicalSignal(
205
+ indicator="BB",
206
+ signal_type="NEUTRAL",
207
+ strength=40,
208
+ description="Preço dentro das bandas: aguardar breakout",
209
+ confidence_impact=5
210
+ )
211
+
212
+
213
+ class MomentumAnalyzer:
214
+ """Analisador de momentum (variação de preço)."""
215
+
216
+ @staticmethod
217
+ def analyze(variation: float) -> TechnicalSignal:
218
+ """Analisa o momentum e retorna sinal técnico."""
219
+ config = TechnicalAnalysisConfig()
220
+
221
+ if abs(variation) >= config.SIGNIFICANT_MOVEMENT_THRESHOLD:
222
+ if variation > 0:
223
+ return TechnicalSignal(
224
+ indicator="MOMENTUM",
225
+ signal_type="BUY",
226
+ strength=60,
227
+ description=f"Momentum positivo (+{variation:.2f}%): seguir tendência",
228
+ confidence_impact=ScoringConfig.MOMENTUM_SCORE
229
+ )
230
+ else:
231
+ return TechnicalSignal(
232
+ indicator="MOMENTUM",
233
+ signal_type="SELL",
234
+ strength=60,
235
+ description=f"Momentum negativo ({variation:.2f}%): seguir tendência",
236
+ confidence_impact=ScoringConfig.MOMENTUM_SCORE
237
+ )
238
+ else:
239
+ return TechnicalSignal(
240
+ indicator="MOMENTUM",
241
+ signal_type="NEUTRAL",
242
+ strength=30,
243
+ description=f"Momentum fraco ({variation:.2f}%)",
244
+ confidence_impact=0
245
+ )
246
+
247
+
248
+ class VolumeAnalyzer:
249
+ """Analisador de volume."""
250
+
251
+ @staticmethod
252
+ def analyze(volume: float) -> TechnicalSignal:
253
+ """Analisa o volume e retorna sinal técnico."""
254
+ config = TechnicalAnalysisConfig()
255
+
256
+ if volume > config.VOLUME_HIGH_THRESHOLD:
257
+ return TechnicalSignal(
258
+ indicator="VOLUME",
259
+ signal_type="NEUTRAL",
260
+ strength=70,
261
+ description=f"Volume alto ({volume:.1f}x): confirma movimento",
262
+ confidence_impact=ScoringConfig.VOLUME_SCORE
263
+ )
264
+ elif volume < config.VOLUME_LOW_THRESHOLD:
265
+ return TechnicalSignal(
266
+ indicator="VOLUME",
267
+ signal_type="NEUTRAL",
268
+ strength=20,
269
+ description=f"Volume baixo ({volume:.1f}x): cuidado com falsos sinais",
270
+ confidence_impact=-ScoringConfig.LOW_VOLUME_PENALTY
271
+ )
272
+ else:
273
+ return TechnicalSignal(
274
+ indicator="VOLUME",
275
+ signal_type="NEUTRAL",
276
+ strength=50,
277
+ description=f"Volume normal ({volume:.1f}x)",
278
+ confidence_impact=0
279
+ )
280
+
281
+
282
+ class ScalpingSetupDetector:
283
+ """Detector de setups específicos para scalping."""
284
+
285
+ @staticmethod
286
+ def detect_perfect_setups(market_data: MarketData, signals: List[TechnicalSignal]) -> List[TechnicalSignal]:
287
+ """Detecta setups perfeitos para scalping."""
288
+ special_signals = []
289
+ config = TechnicalAnalysisConfig()
290
+
291
+ # Setup 1: RSI extremo + EMA contrária = reversão forte
292
+ if ((market_data.rsi <= config.RSI_EXTREME_OVERSOLD and market_data.ema_trend == 'BAIXA') or
293
+ (market_data.rsi >= config.RSI_EXTREME_OVERBOUGHT and market_data.ema_trend == 'ALTA')):
294
+
295
+ special_signals.append(TechnicalSignal(
296
+ indicator="SETUP_REVERSAL",
297
+ signal_type="BUY" if market_data.rsi <= config.RSI_EXTREME_OVERSOLD else "SELL",
298
+ strength=95,
299
+ description="🚨 SINAL FORTE: RSI extremo com EMA contrária - REVERSÃO",
300
+ confidence_impact=ScoringConfig.STRONG_REVERSAL_BONUS
301
+ ))
302
+
303
+ # Setup 2: RSI + BB alinhados
304
+ if market_data.rsi <= 35 and market_data.bb_position == 'ABAIXO':
305
+ special_signals.append(TechnicalSignal(
306
+ indicator="SETUP_PERFECT_BUY",
307
+ signal_type="BUY",
308
+ strength=100,
309
+ description="🎯 SETUP PERFEITO: RSI baixo + BB abaixo - COMPRA FORTE",
310
+ confidence_impact=ScoringConfig.PERFECT_SETUP_BONUS
311
+ ))
312
+
313
+ elif market_data.rsi >= 65 and market_data.bb_position in ['ACIMA', 'SOBRE']:
314
+ special_signals.append(TechnicalSignal(
315
+ indicator="SETUP_PERFECT_SELL",
316
+ signal_type="SELL",
317
+ strength=100,
318
+ description="🎯 SETUP PERFEITO: RSI alto + BB acima - VENDA FORTE",
319
+ confidence_impact=ScoringConfig.PERFECT_SETUP_BONUS
320
+ ))
321
+
322
+ return special_signals
323
+
324
+
325
+ class TechnicalAnalysisEngine:
326
+ """Engine principal de análise técnica."""
327
+
328
+ def __init__(self):
329
+ self.rsi_analyzer = RSIAnalyzer()
330
+ self.ema_analyzer = EMAAnalyzer()
331
+ self.bb_analyzer = BollingerBandsAnalyzer()
332
+ self.momentum_analyzer = MomentumAnalyzer()
333
+ self.volume_analyzer = VolumeAnalyzer()
334
+ self.setup_detector = ScalpingSetupDetector()
335
+
336
+ def analyze(self, market_data: MarketData) -> Dict[str, Any]:
337
+ """Executa análise técnica completa."""
338
+ # Análises individuais
339
+ signals = [
340
+ self.rsi_analyzer.analyze(market_data.rsi),
341
+ self.ema_analyzer.analyze(market_data.ema_trend),
342
+ self.bb_analyzer.analyze(market_data.bb_position),
343
+ self.momentum_analyzer.analyze(market_data.variation),
344
+ self.volume_analyzer.analyze(market_data.volume)
345
+ ]
346
+
347
+ # Detectar setups especiais
348
+ special_signals = self.setup_detector.detect_perfect_setups(market_data, signals)
349
+ all_signals = signals + special_signals
350
+
351
+ # Calcular ação e confiança
352
+ action, confidence = self._calculate_action_and_confidence(all_signals)
353
+
354
+ return {
355
+ 'action': action,
356
+ 'confidence': confidence,
357
+ 'signals': all_signals,
358
+ 'market_data': market_data
359
+ }
360
+
361
+ def _calculate_action_and_confidence(self, signals: List[TechnicalSignal]) -> tuple[str, int]:
362
+ """Calcula a ação recomendada e nível de confiança."""
363
+ buy_score = 0
364
+ sell_score = 0
365
+ confidence_score = 0
366
+
367
+ # Somar pontuações por tipo de sinal
368
+ for signal in signals:
369
+ confidence_score += signal.confidence_impact
370
+
371
+ if signal.signal_type == "BUY":
372
+ buy_score += signal.strength
373
+ elif signal.signal_type == "SELL":
374
+ sell_score += signal.strength
375
+
376
+ # Determinar ação baseada nas pontuações
377
+ if buy_score > sell_score and buy_score > 100:
378
+ action = "COMPRAR"
379
+ elif sell_score > buy_score and sell_score > 100:
380
+ action = "VENDER"
381
+ else:
382
+ action = "AGUARDAR"
383
+
384
+ # Aplicar penalidade por conflito
385
+ if abs(buy_score - sell_score) < 50 and max(buy_score, sell_score) > 100:
386
+ confidence_score -= ScoringConfig.CONFLICT_PENALTY
387
+
388
+ # Limitar confiança
389
+ confidence_score = max(ScoringConfig.MIN_CONFIDENCE,
390
+ min(ScoringConfig.MAX_CONFIDENCE, confidence_score))
391
+
392
+ return action, confidence_score
393
+
394
+
395
+ class RiskCalculator:
396
+ """Calculadora de risco para trading."""
397
+
398
+ @staticmethod
399
+ def calculate_stop_loss(price: float, action: str) -> float:
400
+ """Calcula stop loss baseado no preço e ação."""
401
+ config = TradingConfig()
402
+ stop_distance = price * config.STOP_LOSS_PERCENTAGE
403
+
404
+ if action == "COMPRAR":
405
+ return price - stop_distance
406
+ elif action == "VENDER":
407
+ return price + stop_distance
408
+ else:
409
+ return 0
410
+
411
+ @staticmethod
412
+ def calculate_take_profit(price: float, action: str) -> float:
413
+ """Calcula take profit baseado no preço e ação."""
414
+ config = TradingConfig()
415
+ profit_distance = price * config.TAKE_PROFIT_PERCENTAGE
416
+
417
+ if action == "COMPRAR":
418
+ return price + profit_distance
419
+ elif action == "VENDER":
420
+ return price - profit_distance
421
+ else:
422
+ return 0
423
+
424
+ @staticmethod
425
+ def get_risk_reward_ratio() -> float:
426
+ """Retorna a relação risco/recompensa configurada."""
427
+ return TradingConfig.RISK_REWARD_RATIO
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ gradio==4.44.0
2
+ transformers==4.36.0
3
+ torch>=2.0.0
4
+ numpy>=1.24.0
5
+ pandas>=2.0.0
6
+ scipy>=1.11.0
sentiment_analysis.py ADDED
@@ -0,0 +1,331 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Módulo de análise de sentimento usando IA financeira."""
2
+
3
+ import re
4
+ from typing import Dict, Optional, Any
5
+ from dataclasses import dataclass
6
+
7
+ from config import FINANCIAL_MODELS, AIConfig, AppConfig
8
+
9
+ # Importações opcionais para IA
10
+ try:
11
+ from transformers import pipeline
12
+ import torch
13
+ TRANSFORMERS_AVAILABLE = True
14
+ except ImportError:
15
+ TRANSFORMERS_AVAILABLE = False
16
+ print(AppConfig.STATUS_MESSAGES['AI_UNAVAILABLE'])
17
+
18
+
19
+ @dataclass
20
+ class SentimentResult:
21
+ """Classe para representar resultado de análise de sentimento."""
22
+ sentiment: str # 'positive', 'negative', 'neutral'
23
+ confidence: float # 0.0 - 1.0
24
+ label: str # 'POSITIVO', 'NEGATIVO', 'NEUTRO'
25
+ model_used: Optional[str] = None
26
+
27
+
28
+ class ModelManager:
29
+ """Gerenciador de modelos de IA."""
30
+
31
+ def __init__(self):
32
+ self.sentiment_pipeline = None
33
+ self.current_model_info = None
34
+ self.is_available = TRANSFORMERS_AVAILABLE
35
+
36
+ if self.is_available:
37
+ self._load_models()
38
+
39
+ def _load_models(self) -> None:
40
+ """Tenta carregar modelos em ordem de prioridade."""
41
+ for model_config in FINANCIAL_MODELS:
42
+ try:
43
+ print(AppConfig.STATUS_MESSAGES['AI_LOADING'].format(
44
+ model_config['description']
45
+ ))
46
+
47
+ self.sentiment_pipeline = pipeline(
48
+ AIConfig.PIPELINE_CONFIG['task'],
49
+ model=model_config["name"],
50
+ return_all_scores=AIConfig.PIPELINE_CONFIG['return_all_scores']
51
+ )
52
+
53
+ self.current_model_info = model_config
54
+ print(AppConfig.STATUS_MESSAGES['AI_SUCCESS'].format(
55
+ model_config['description']
56
+ ))
57
+ break
58
+
59
+ except Exception as e:
60
+ print(AppConfig.STATUS_MESSAGES['AI_FAILED'].format(
61
+ model_config['name'], str(e)
62
+ ))
63
+ continue
64
+
65
+ if self.sentiment_pipeline is None:
66
+ print(AppConfig.STATUS_MESSAGES['NO_MODEL_LOADED'])
67
+ self.is_available = False
68
+
69
+ def get_model_info(self) -> Optional[Dict[str, Any]]:
70
+ """Retorna informações do modelo atual."""
71
+ return self.current_model_info
72
+
73
+ def is_model_available(self) -> bool:
74
+ """Verifica se há modelo disponível."""
75
+ return self.is_available and self.sentiment_pipeline is not None
76
+
77
+
78
+ class TextPreprocessor:
79
+ """Pré-processador de texto para análise de sentimento."""
80
+
81
+ @staticmethod
82
+ def clean_text(text: str) -> str:
83
+ """Limpa e prepara texto para análise."""
84
+ if not text:
85
+ return ""
86
+
87
+ # Remover caracteres especiais, manter apenas palavras, espaços e alguns símbolos
88
+ clean_text = re.sub(r'[^\w\s\+\-\%\.]', ' ', text)
89
+
90
+ # Limitar tamanho para o modelo
91
+ clean_text = clean_text[:AIConfig.MAX_TEXT_LENGTH]
92
+
93
+ # Remover espaços extras
94
+ clean_text = ' '.join(clean_text.split())
95
+
96
+ return clean_text
97
+
98
+ @staticmethod
99
+ def extract_financial_keywords(text: str) -> Dict[str, int]:
100
+ """Extrai palavras-chave financeiras do texto."""
101
+ financial_keywords = {
102
+ 'positive': ['alta', 'subida', 'ganho', 'lucro', 'crescimento', 'otimista', 'positivo'],
103
+ 'negative': ['baixa', 'queda', 'perda', 'prejuízo', 'declínio', 'pessimista', 'negativo'],
104
+ 'neutral': ['estável', 'neutro', 'lateral', 'consolidação']
105
+ }
106
+
107
+ text_lower = text.lower()
108
+ keyword_counts = {'positive': 0, 'negative': 0, 'neutral': 0}
109
+
110
+ for category, keywords in financial_keywords.items():
111
+ for keyword in keywords:
112
+ keyword_counts[category] += text_lower.count(keyword)
113
+
114
+ return keyword_counts
115
+
116
+
117
+ class SentimentAnalyzer:
118
+ """Analisador de sentimento principal."""
119
+
120
+ def __init__(self, model_manager: ModelManager):
121
+ self.model_manager = model_manager
122
+ self.preprocessor = TextPreprocessor()
123
+
124
+ def analyze(self, text: str) -> SentimentResult:
125
+ """Analisa o sentimento do texto."""
126
+ if not self.model_manager.is_model_available():
127
+ return self._get_fallback_sentiment(text)
128
+
129
+ try:
130
+ # Pré-processar texto
131
+ clean_text = self.preprocessor.clean_text(text)
132
+
133
+ if not clean_text.strip():
134
+ return SentimentResult(
135
+ sentiment='neutral',
136
+ confidence=0.5,
137
+ label='NEUTRO',
138
+ model_used='fallback'
139
+ )
140
+
141
+ # Executar análise de sentimento
142
+ result = self.model_manager.sentiment_pipeline(clean_text)
143
+
144
+ # Processar resultado
145
+ return self._process_model_result(result)
146
+
147
+ except Exception as e:
148
+ print(f"Erro na análise de sentimento: {e}")
149
+ return self._get_fallback_sentiment(text)
150
+
151
+ def _process_model_result(self, result: Any) -> SentimentResult:
152
+ """Processa resultado do modelo de IA."""
153
+ try:
154
+ # Processar resultado baseado no formato
155
+ if isinstance(result, list) and len(result) > 0:
156
+ # Se return_all_scores=True, pegar o resultado com maior score
157
+ if isinstance(result[0], list):
158
+ predictions = result[0]
159
+ best_prediction = max(predictions, key=lambda x: x['score'])
160
+ else:
161
+ best_prediction = result[0]
162
+
163
+ # Mapear label usando o mapeamento do modelo atual
164
+ label = best_prediction['label']
165
+ confidence = best_prediction['score']
166
+
167
+ # Usar mapeamento específico do modelo ou fallback genérico
168
+ model_info = self.model_manager.get_model_info()
169
+ if model_info and label in model_info['labels']:
170
+ sentiment_label = model_info['labels'][label]
171
+ else:
172
+ # Fallback para mapeamento genérico
173
+ sentiment_label = self._map_generic_label(label)
174
+
175
+ return SentimentResult(
176
+ sentiment=label.lower(),
177
+ confidence=confidence,
178
+ label=sentiment_label,
179
+ model_used=model_info['name'] if model_info else 'unknown'
180
+ )
181
+
182
+ # Fallback se resultado não esperado
183
+ return SentimentResult(
184
+ sentiment='neutral',
185
+ confidence=0.5,
186
+ label='NEUTRO',
187
+ model_used='fallback'
188
+ )
189
+
190
+ except Exception as e:
191
+ print(f"Erro ao processar resultado do modelo: {e}")
192
+ return SentimentResult(
193
+ sentiment='neutral',
194
+ confidence=0.5,
195
+ label='NEUTRO',
196
+ model_used='error_fallback'
197
+ )
198
+
199
+ def _map_generic_label(self, label: str) -> str:
200
+ """Mapeia labels genéricos para formato padrão."""
201
+ label_lower = label.lower()
202
+
203
+ if 'neg' in label_lower or 'bad' in label_lower:
204
+ return 'NEGATIVO'
205
+ elif 'pos' in label_lower or 'good' in label_lower:
206
+ return 'POSITIVO'
207
+ else:
208
+ return 'NEUTRO'
209
+
210
+ def _get_fallback_sentiment(self, text: str) -> SentimentResult:
211
+ """Análise de sentimento baseada em palavras-chave (fallback)."""
212
+ if not text:
213
+ return SentimentResult(
214
+ sentiment='neutral',
215
+ confidence=0.5,
216
+ label='NEUTRO',
217
+ model_used='keyword_fallback'
218
+ )
219
+
220
+ # Análise baseada em palavras-chave
221
+ keyword_counts = self.preprocessor.extract_financial_keywords(text)
222
+
223
+ total_keywords = sum(keyword_counts.values())
224
+ if total_keywords == 0:
225
+ return SentimentResult(
226
+ sentiment='neutral',
227
+ confidence=0.5,
228
+ label='NEUTRO',
229
+ model_used='keyword_fallback'
230
+ )
231
+
232
+ # Determinar sentimento dominante
233
+ max_category = max(keyword_counts, key=keyword_counts.get)
234
+ max_count = keyword_counts[max_category]
235
+ confidence = min(0.8, max_count / total_keywords) # Máximo 80% de confiança
236
+
237
+ sentiment_mapping = {
238
+ 'positive': ('positive', 'POSITIVO'),
239
+ 'negative': ('negative', 'NEGATIVO'),
240
+ 'neutral': ('neutral', 'NEUTRO')
241
+ }
242
+
243
+ sentiment, label = sentiment_mapping[max_category]
244
+
245
+ return SentimentResult(
246
+ sentiment=sentiment,
247
+ confidence=confidence,
248
+ label=label,
249
+ model_used='keyword_fallback'
250
+ )
251
+
252
+
253
+ class SentimentScorer:
254
+ """Calculador de pontuação baseada em sentimento."""
255
+
256
+ @staticmethod
257
+ def calculate_sentiment_score(sentiment_result: SentimentResult) -> int:
258
+ """Calcula pontuação de confiança baseada no sentimento."""
259
+ from config import ScoringConfig
260
+
261
+ base_score = int(sentiment_result.confidence * ScoringConfig.SENTIMENT_MAX_SCORE)
262
+
263
+ # Bonificação por modelo de IA vs fallback
264
+ if sentiment_result.model_used and 'fallback' not in sentiment_result.model_used:
265
+ base_score = int(base_score * 1.2) # 20% de bonificação para modelos de IA
266
+
267
+ return min(base_score, ScoringConfig.SENTIMENT_MAX_SCORE)
268
+
269
+ @staticmethod
270
+ def get_sentiment_signal_description(sentiment_result: SentimentResult) -> str:
271
+ """Gera descrição do sinal de sentimento."""
272
+ confidence_pct = sentiment_result.confidence * 100
273
+
274
+ if sentiment_result.label == 'POSITIVO':
275
+ bias = "viés de COMPRA"
276
+ elif sentiment_result.label == 'NEGATIVO':
277
+ bias = "viés de VENDA"
278
+ else:
279
+ bias = "sem viés claro"
280
+
281
+ model_indicator = "🤖 IA" if 'fallback' not in (sentiment_result.model_used or '') else "📝 Palavras-chave"
282
+
283
+ return f"{model_indicator} Sentimento: {sentiment_result.label} ({confidence_pct:.1f}%): {bias}"
284
+
285
+
286
+ class SentimentAnalysisEngine:
287
+ """Engine principal de análise de sentimento."""
288
+
289
+ def __init__(self):
290
+ self.model_manager = ModelManager()
291
+ self.analyzer = SentimentAnalyzer(self.model_manager)
292
+ self.scorer = SentimentScorer()
293
+
294
+ def analyze_text(self, text: str) -> Dict[str, Any]:
295
+ """Executa análise completa de sentimento."""
296
+ # Análise de sentimento
297
+ sentiment_result = self.analyzer.analyze(text)
298
+
299
+ # Calcular pontuação
300
+ score = self.scorer.calculate_sentiment_score(sentiment_result)
301
+
302
+ # Gerar descrição
303
+ description = self.scorer.get_sentiment_signal_description(sentiment_result)
304
+
305
+ return {
306
+ 'result': sentiment_result,
307
+ 'score': score,
308
+ 'description': description
309
+ }
310
+
311
+ def get_model_status(self) -> Dict[str, Any]:
312
+ """Retorna status do modelo atual."""
313
+ if self.model_manager.is_model_available():
314
+ model_info = self.model_manager.get_model_info()
315
+ return {
316
+ 'available': True,
317
+ 'model_name': model_info['name'] if model_info else 'Unknown',
318
+ 'description': model_info['description'] if model_info else 'Unknown Model',
319
+ 'status': 'active'
320
+ }
321
+ else:
322
+ return {
323
+ 'available': False,
324
+ 'model_name': None,
325
+ 'description': 'IA indisponível - usando análise por palavras-chave',
326
+ 'status': 'fallback'
327
+ }
328
+
329
+ def is_available(self) -> bool:
330
+ """Verifica se análise de IA está disponível."""
331
+ return self.model_manager.is_model_available()
ui.py ADDED
@@ -0,0 +1,476 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Módulo de interface do usuário com Gradio."""
2
+
3
+ import gradio as gr
4
+ from typing import Dict, Any, Optional, Tuple
5
+
6
+ from config import UIConfig, AppConfig
7
+ from utils import (
8
+ DateTimeUtils,
9
+ NumberUtils,
10
+ ConfidenceUtils,
11
+ ActionUtils,
12
+ SentimentUtils,
13
+ FormatUtils
14
+ )
15
+
16
+
17
+ class UIComponents:
18
+ """Componentes da interface do usuário."""
19
+
20
+ @staticmethod
21
+ def create_header() -> str:
22
+ """Cria cabeçalho da aplicação."""
23
+ return f"""
24
+ <div style="text-align: center; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 10px; margin-bottom: 20px;">
25
+ <h1 style="color: white; margin: 0; font-size: 2.5em; text-shadow: 2px 2px 4px rgba(0,0,0,0.3);">
26
+ 📈 {AppConfig.APP_TITLE}
27
+ </h1>
28
+ <p style="color: #f0f0f0; margin: 10px 0 0 0; font-size: 1.2em;">
29
+ {AppConfig.APP_DESCRIPTION}
30
+ </p>
31
+ </div>
32
+ """
33
+
34
+ @staticmethod
35
+ def create_input_section() -> gr.Column:
36
+ """Cria seção de entrada de dados."""
37
+ with gr.Column() as input_section:
38
+ gr.HTML("""
39
+ <div style="background: #f8f9fa; padding: 15px; border-radius: 8px; border-left: 4px solid #007bff;">
40
+ <h3 style="margin: 0 0 10px 0; color: #495057;">📊 Dados de Mercado</h3>
41
+ <p style="margin: 0; color: #6c757d; font-size: 0.9em;">
42
+ Cole os dados do ativo ou digite manualmente os valores
43
+ </p>
44
+ </div>
45
+ """)
46
+
47
+ market_input = gr.Textbox(
48
+ label="Dados do Mercado",
49
+ placeholder=AppConfig.EXAMPLE_INPUT,
50
+ lines=8,
51
+ max_lines=15
52
+ )
53
+
54
+ analyze_btn = gr.Button(
55
+ "🔍 Analisar Mercado",
56
+ variant="primary",
57
+ size="lg"
58
+ )
59
+
60
+ return input_section, market_input, analyze_btn
61
+
62
+ @staticmethod
63
+ def create_output_section() -> Tuple[gr.Column, Dict[str, Any]]:
64
+ """Cria seção de saída de resultados."""
65
+ outputs = {}
66
+
67
+ with gr.Column() as output_section:
68
+ # Status do modelo de IA
69
+ outputs['ai_status'] = gr.HTML(
70
+ UIComponents._get_ai_status_html(available=False)
71
+ )
72
+
73
+ # Resultado principal
74
+ outputs['main_result'] = gr.HTML()
75
+
76
+ # Abas de detalhes
77
+ with gr.Tabs():
78
+ # Aba de Análise Técnica
79
+ with gr.Tab("📊 Análise Técnica"):
80
+ outputs['technical_analysis'] = gr.HTML()
81
+
82
+ # Aba de Análise de Sentimento
83
+ with gr.Tab("🧠 Análise de Sentimento"):
84
+ outputs['sentiment_analysis'] = gr.HTML()
85
+
86
+ # Aba de Recomendações
87
+ with gr.Tab("💡 Recomendações"):
88
+ outputs['recommendations'] = gr.HTML()
89
+
90
+ # Aba de Dados Brutos
91
+ with gr.Tab("🔍 Dados Detalhados"):
92
+ outputs['raw_data'] = gr.JSON()
93
+
94
+ return output_section, outputs
95
+
96
+ @staticmethod
97
+ def create_footer(model_info: Optional[Dict[str, Any]] = None) -> str:
98
+ """Cria rodapé da aplicação."""
99
+ timestamp = DateTimeUtils.get_current_datetime()
100
+
101
+ if model_info and model_info.get('available', False):
102
+ ai_status = f"🤖 IA: {model_info.get('description', 'Modelo Ativo')}"
103
+ else:
104
+ ai_status = "⚠️ IA: Indisponível (apenas análise técnica)"
105
+
106
+ return f"""
107
+ <div style="text-align: center; padding: 15px; background: #f8f9fa; border-radius: 8px; margin-top: 20px; border-top: 2px solid #dee2e6;">
108
+ <p style="margin: 0; color: #6c757d; font-size: 0.9em;">
109
+ {ai_status} | ⏰ Última atualização: {timestamp}
110
+ </p>
111
+ <p style="margin: 5px 0 0 0; color: #adb5bd; font-size: 0.8em;">
112
+ Desenvolvido para análise de scalping no mercado financeiro
113
+ </p>
114
+ </div>
115
+ """
116
+
117
+ @staticmethod
118
+ def _get_ai_status_html(available: bool, model_description: str = "") -> str:
119
+ """Gera HTML para status da IA."""
120
+ if available:
121
+ return f"""
122
+ <div style="background: #d4edda; border: 1px solid #c3e6cb; border-radius: 8px; padding: 12px; margin-bottom: 15px;">
123
+ <div style="display: flex; align-items: center; gap: 10px;">
124
+ <span style="font-size: 1.2em;">🤖</span>
125
+ <div>
126
+ <strong style="color: #155724;">IA Ativa:</strong>
127
+ <span style="color: #155724;">{model_description}</span>
128
+ </div>
129
+ </div>
130
+ </div>
131
+ """
132
+ else:
133
+ return """
134
+ <div style="background: #fff3cd; border: 1px solid #ffeaa7; border-radius: 8px; padding: 12px; margin-bottom: 15px;">
135
+ <div style="display: flex; align-items: center; gap: 10px;">
136
+ <span style="font-size: 1.2em;">⚠️</span>
137
+ <div>
138
+ <strong style="color: #856404;">IA Indisponível:</strong>
139
+ <span style="color: #856404;">Executando apenas análise técnica</span>
140
+ </div>
141
+ </div>
142
+ </div>
143
+ """
144
+
145
+
146
+ class ResultFormatter:
147
+ """Formatador de resultados para a interface."""
148
+
149
+ @staticmethod
150
+ def format_main_result(analysis_result: Dict[str, Any]) -> str:
151
+ """Formata resultado principal da análise."""
152
+ action = analysis_result.get('action', 'AGUARDAR')
153
+ confidence = analysis_result.get('confidence', 0)
154
+ market_data = analysis_result.get('market_data', {})
155
+
156
+ # Obter informações da ação
157
+ action_emojis = ActionUtils.get_action_emojis(action)
158
+ action_color = ActionUtils.get_action_color(action)
159
+ confidence_level = ConfidenceUtils.get_confidence_level(confidence)
160
+ confidence_bar = ConfidenceUtils.generate_confidence_bar(confidence)
161
+
162
+ # Formatação do preço
163
+ price = market_data.get('price', 0)
164
+ variation = market_data.get('variation', 0)
165
+ formatted_price = NumberUtils.format_price(price)
166
+ formatted_variation = NumberUtils.format_percentage(variation)
167
+
168
+ # Cor da variação
169
+ variation_color = "#28a745" if variation >= 0 else "#dc3545"
170
+
171
+ return f"""
172
+ <div style="background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); border-radius: 12px; padding: 25px; margin: 15px 0; border: 2px solid #dee2e6;">
173
+ <div style="text-align: center; margin-bottom: 20px;">
174
+ <div style="font-size: 3em; margin-bottom: 10px;">{action_emojis['main']}</div>
175
+ <h2 style="margin: 0; color: {action_color}; font-size: 2em; text-transform: uppercase; letter-spacing: 1px;">
176
+ {action_emojis['action']} {action}
177
+ </h2>
178
+ </div>
179
+
180
+ <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 20px;">
181
+ <div style="text-align: center; padding: 15px; background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
182
+ <div style="font-size: 0.9em; color: #6c757d; margin-bottom: 5px;">PREÇO ATUAL</div>
183
+ <div style="font-size: 1.8em; font-weight: bold; color: #495057;">{formatted_price}</div>
184
+ <div style="font-size: 1.1em; color: {variation_color}; font-weight: 600;">{formatted_variation}</div>
185
+ </div>
186
+
187
+ <div style="text-align: center; padding: 15px; background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
188
+ <div style="font-size: 0.9em; color: #6c757d; margin-bottom: 5px;">CONFIANÇA</div>
189
+ <div style="font-size: 1.8em; font-weight: bold; color: #495057;">{confidence}%</div>
190
+ <div style="font-size: 0.9em; color: #6c757d;">{confidence_level}</div>
191
+ </div>
192
+ </div>
193
+
194
+ <div style="text-align: center; margin-top: 15px;">
195
+ <div style="font-size: 0.9em; color: #6c757d; margin-bottom: 8px;">NÍVEL DE CONFIANÇA</div>
196
+ <div style="font-family: monospace; font-size: 1.2em; letter-spacing: 2px; color: #495057;">{confidence_bar}</div>
197
+ </div>
198
+ </div>
199
+ """
200
+
201
+ @staticmethod
202
+ def format_technical_analysis(analysis_result: Dict[str, Any]) -> str:
203
+ """Formata análise técnica."""
204
+ market_data = analysis_result.get('market_data', {})
205
+ signals = analysis_result.get('signals', [])
206
+
207
+ # Resumo dos dados de mercado
208
+ market_summary = FormatUtils.format_market_summary(market_data)
209
+
210
+ # Lista de sinais
211
+ signals_list = FormatUtils.format_signal_list(signals)
212
+
213
+ return f"""
214
+ <div style="background: white; border-radius: 8px; padding: 20px; border: 1px solid #dee2e6;">
215
+ <h3 style="color: #495057; margin-top: 0; border-bottom: 2px solid #007bff; padding-bottom: 10px;">
216
+ 📊 Indicadores Técnicos
217
+ </h3>
218
+
219
+ <div style="background: #f8f9fa; padding: 15px; border-radius: 6px; margin-bottom: 20px;">
220
+ {market_summary}
221
+ </div>
222
+
223
+ <h4 style="color: #495057; margin-bottom: 15px;">🎯 Sinais Detectados</h4>
224
+ <div style="background: #f8f9fa; padding: 15px; border-radius: 6px; font-family: monospace; white-space: pre-line;">
225
+ {signals_list}
226
+ </div>
227
+ </div>
228
+ """
229
+
230
+ @staticmethod
231
+ def format_sentiment_analysis(analysis_result: Dict[str, Any]) -> str:
232
+ """Formata análise de sentimento."""
233
+ sentiment = analysis_result.get('sentiment', {})
234
+
235
+ if not sentiment:
236
+ return """
237
+ <div style="background: white; border-radius: 8px; padding: 20px; border: 1px solid #dee2e6;">
238
+ <h3 style="color: #495057; margin-top: 0;">🧠 Análise de Sentimento</h3>
239
+ <div style="text-align: center; padding: 30px; color: #6c757d;">
240
+ <div style="font-size: 2em; margin-bottom: 10px;">⚠️</div>
241
+ <p>Análise de sentimento não disponível</p>
242
+ <p style="font-size: 0.9em;">Instale as dependências de IA para ativar esta funcionalidade</p>
243
+ </div>
244
+ </div>
245
+ """
246
+
247
+ label = sentiment.get('label', 'NEUTRO')
248
+ confidence = sentiment.get('confidence', 0)
249
+ emoji = SentimentUtils.get_sentiment_emoji(label)
250
+
251
+ # Cor baseada no sentimento
252
+ sentiment_colors = {
253
+ 'POSITIVO': '#28a745',
254
+ 'NEGATIVO': '#dc3545',
255
+ 'NEUTRO': '#ffc107'
256
+ }
257
+ color = sentiment_colors.get(label, '#6c757d')
258
+
259
+ return f"""
260
+ <div style="background: white; border-radius: 8px; padding: 20px; border: 1px solid #dee2e6;">
261
+ <h3 style="color: #495057; margin-top: 0; border-bottom: 2px solid #28a745; padding-bottom: 10px;">
262
+ 🧠 Análise de Sentimento
263
+ </h3>
264
+
265
+ <div style="text-align: center; padding: 20px;">
266
+ <div style="font-size: 3em; margin-bottom: 15px;">{emoji}</div>
267
+ <h4 style="color: {color}; margin: 0; font-size: 1.5em; text-transform: uppercase;">{label}</h4>
268
+ <div style="margin-top: 10px; color: #6c757d;">Confiança: {confidence:.1f}%</div>
269
+ </div>
270
+
271
+ <div style="background: #f8f9fa; padding: 15px; border-radius: 6px; margin-top: 15px;">
272
+ <h5 style="margin-top: 0; color: #495057;">📝 Detalhes da Análise</h5>
273
+ <p style="margin: 0; color: #6c757d; font-size: 0.9em;">
274
+ O modelo de IA analisou o contexto do mercado e determinou um sentimento <strong>{label.lower()}</strong>
275
+ com {confidence:.1f}% de confiança.
276
+ </p>
277
+ </div>
278
+ </div>
279
+ """
280
+
281
+ @staticmethod
282
+ def format_recommendations(analysis_result: Dict[str, Any]) -> str:
283
+ """Formata recomendações de trading."""
284
+ action = analysis_result.get('action', 'AGUARDAR')
285
+ market_data = analysis_result.get('market_data', {})
286
+ price = market_data.get('price', 0)
287
+
288
+ # Recomendações de trading
289
+ trading_recs = FormatUtils.format_trading_recommendations(action, price)
290
+
291
+ # Direção de trading
292
+ direction = ActionUtils.get_trading_direction(action)
293
+ direction_emoji = "📈" if direction == "COMPRA" else "📉" if direction == "VENDA" else "⏸️"
294
+
295
+ return f"""
296
+ <div style="background: white; border-radius: 8px; padding: 20px; border: 1px solid #dee2e6;">
297
+ <h3 style="color: #495057; margin-top: 0; border-bottom: 2px solid #ffc107; padding-bottom: 10px;">
298
+ 💡 Recomendações de Trading
299
+ </h3>
300
+
301
+ <div style="background: #fff3cd; border: 1px solid #ffeaa7; border-radius: 6px; padding: 15px; margin-bottom: 20px;">
302
+ <div style="display: flex; align-items: center; gap: 10px; margin-bottom: 10px;">
303
+ <span style="font-size: 1.5em;">{direction_emoji}</span>
304
+ <strong style="color: #856404;">Direção: {direction}</strong>
305
+ </div>
306
+ </div>
307
+
308
+ <div style="background: #f8f9fa; padding: 15px; border-radius: 6px; white-space: pre-line;">
309
+ {trading_recs}
310
+ </div>
311
+
312
+ <div style="background: #d1ecf1; border: 1px solid #bee5eb; border-radius: 6px; padding: 15px; margin-top: 15px;">
313
+ <h5 style="margin-top: 0; color: #0c5460;">⚠️ Aviso Importante</h5>
314
+ <p style="margin: 0; color: #0c5460; font-size: 0.9em;">
315
+ Esta análise é apenas para fins educacionais. Sempre faça sua própria pesquisa e
316
+ considere consultar um consultor financeiro antes de tomar decisões de investimento.
317
+ </p>
318
+ </div>
319
+ </div>
320
+ """
321
+
322
+
323
+ class GradioInterface:
324
+ """Interface principal do Gradio."""
325
+
326
+ def __init__(self, analysis_function, model_info: Optional[Dict[str, Any]] = None):
327
+ """Inicializa interface."""
328
+ self.analysis_function = analysis_function
329
+ self.model_info = model_info or {'available': False}
330
+ self.interface = None
331
+
332
+ def create_interface(self) -> gr.Blocks:
333
+ """Cria interface completa do Gradio."""
334
+ with gr.Blocks(
335
+ title=AppConfig.APP_TITLE,
336
+ theme=gr.themes.Soft(),
337
+ css=self._get_custom_css()
338
+ ) as interface:
339
+ # Cabeçalho
340
+ gr.HTML(UIComponents.create_header())
341
+
342
+ # Layout principal
343
+ with gr.Row():
344
+ # Coluna de entrada (40%)
345
+ with gr.Column(scale=2):
346
+ input_section, market_input, analyze_btn = UIComponents.create_input_section()
347
+
348
+ # Coluna de saída (60%)
349
+ with gr.Column(scale=3):
350
+ output_section, outputs = UIComponents.create_output_section()
351
+
352
+ # Rodapé
353
+ gr.HTML(UIComponents.create_footer(self.model_info))
354
+
355
+ # Configurar evento de análise
356
+ analyze_btn.click(
357
+ fn=self._analyze_wrapper,
358
+ inputs=[market_input],
359
+ outputs=[
360
+ outputs['ai_status'],
361
+ outputs['main_result'],
362
+ outputs['technical_analysis'],
363
+ outputs['sentiment_analysis'],
364
+ outputs['recommendations'],
365
+ outputs['raw_data']
366
+ ]
367
+ )
368
+
369
+ # Atualizar status da IA na inicialização
370
+ interface.load(
371
+ fn=lambda: UIComponents._get_ai_status_html(
372
+ self.model_info.get('available', False),
373
+ self.model_info.get('description', '')
374
+ ),
375
+ outputs=[outputs['ai_status']]
376
+ )
377
+
378
+ self.interface = interface
379
+ return interface
380
+
381
+ def _analyze_wrapper(self, market_input: str) -> Tuple[str, str, str, str, str, Dict[str, Any]]:
382
+ """Wrapper para função de análise com formatação de saída."""
383
+ try:
384
+ # Executar análise
385
+ analysis_result = self.analysis_function(market_input)
386
+
387
+ # Formatear resultados
388
+ ai_status = UIComponents._get_ai_status_html(
389
+ self.model_info.get('available', False),
390
+ self.model_info.get('description', '')
391
+ )
392
+
393
+ main_result = ResultFormatter.format_main_result(analysis_result)
394
+ technical_analysis = ResultFormatter.format_technical_analysis(analysis_result)
395
+ sentiment_analysis = ResultFormatter.format_sentiment_analysis(analysis_result)
396
+ recommendations = ResultFormatter.format_recommendations(analysis_result)
397
+
398
+ # Dados brutos para debug
399
+ raw_data = {
400
+ 'timestamp': DateTimeUtils.get_current_datetime(),
401
+ 'analysis_result': analysis_result
402
+ }
403
+
404
+ return (
405
+ ai_status,
406
+ main_result,
407
+ technical_analysis,
408
+ sentiment_analysis,
409
+ recommendations,
410
+ raw_data
411
+ )
412
+
413
+ except Exception as e:
414
+ error_msg = f"Erro na análise: {str(e)}"
415
+ error_html = f"""
416
+ <div style="background: #f8d7da; border: 1px solid #f5c6cb; border-radius: 8px; padding: 15px; color: #721c24;">
417
+ <h4 style="margin-top: 0;">❌ Erro na Análise</h4>
418
+ <p style="margin: 0;">{error_msg}</p>
419
+ </div>
420
+ """
421
+
422
+ return (
423
+ UIComponents._get_ai_status_html(False),
424
+ error_html,
425
+ error_html,
426
+ error_html,
427
+ error_html,
428
+ {'error': error_msg}
429
+ )
430
+
431
+ def _get_custom_css(self) -> str:
432
+ """Retorna CSS customizado para a interface."""
433
+ return """
434
+ .gradio-container {
435
+ max-width: 1200px !important;
436
+ margin: auto !important;
437
+ }
438
+
439
+ .gr-button {
440
+ transition: all 0.3s ease !important;
441
+ }
442
+
443
+ .gr-button:hover {
444
+ transform: translateY(-2px) !important;
445
+ box-shadow: 0 4px 8px rgba(0,0,0,0.2) !important;
446
+ }
447
+
448
+ .gr-textbox textarea {
449
+ font-family: 'Courier New', monospace !important;
450
+ }
451
+
452
+ .gr-tab-nav {
453
+ background: #f8f9fa !important;
454
+ }
455
+
456
+ .gr-tab-nav button {
457
+ border-radius: 8px 8px 0 0 !important;
458
+ }
459
+ """
460
+
461
+ def launch(self, **kwargs) -> None:
462
+ """Lança a interface."""
463
+ if not self.interface:
464
+ self.create_interface()
465
+
466
+ default_kwargs = {
467
+ 'server_name': '127.0.0.1',
468
+ 'server_port': 7860,
469
+ 'share': False,
470
+ 'show_error': True
471
+ }
472
+
473
+ # Mesclar argumentos padrão com os fornecidos
474
+ launch_kwargs = {**default_kwargs, **kwargs}
475
+
476
+ self.interface.launch(**launch_kwargs)
utils.py ADDED
@@ -0,0 +1,330 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Módulo de utilitários e funções auxiliares."""
2
+
3
+ import json
4
+ from datetime import datetime
5
+ from typing import Dict, Any, Optional
6
+
7
+ from config import (
8
+ TradingConfig,
9
+ UIConfig,
10
+ ScoringConfig
11
+ )
12
+
13
+
14
+ class DateTimeUtils:
15
+ """Utilitários para manipulação de data e hora."""
16
+
17
+ @staticmethod
18
+ def get_current_timestamp() -> str:
19
+ """Retorna timestamp atual formatado."""
20
+ return datetime.now().strftime("%H:%M:%S")
21
+
22
+ @staticmethod
23
+ def get_current_datetime() -> str:
24
+ """Retorna data e hora atual formatada."""
25
+ return datetime.now().strftime("%d/%m/%Y %H:%M:%S")
26
+
27
+ @staticmethod
28
+ def format_timestamp(dt: datetime) -> str:
29
+ """Formata datetime para timestamp."""
30
+ return dt.strftime("%H:%M:%S")
31
+
32
+
33
+ class NumberUtils:
34
+ """Utilitários para manipulação de números."""
35
+
36
+ @staticmethod
37
+ def format_price(price: float) -> str:
38
+ """Formata preço com separadores de milhares."""
39
+ return f"{price:,.0f}"
40
+
41
+ @staticmethod
42
+ def format_percentage(value: float) -> str:
43
+ """Formata porcentagem com sinal."""
44
+ return f"{value:+.2f}%"
45
+
46
+ @staticmethod
47
+ def format_volume(volume: float) -> str:
48
+ """Formata volume com uma casa decimal."""
49
+ return f"{volume:.1f}x"
50
+
51
+ @staticmethod
52
+ def calculate_points_from_percentage(price: float, percentage: float) -> float:
53
+ """Calcula pontos baseado em porcentagem do preço."""
54
+ return price * (percentage / 100)
55
+
56
+
57
+ class ConfidenceUtils:
58
+ """Utilitários para manipulação de níveis de confiança."""
59
+
60
+ @staticmethod
61
+ def get_confidence_level(confidence: int) -> str:
62
+ """Retorna nível de confiança textual."""
63
+ config = TradingConfig.CONFIDENCE_LEVELS
64
+
65
+ if confidence >= config['MUITO_ALTA']:
66
+ return "MUITO ALTA"
67
+ elif confidence >= config['ALTA']:
68
+ return "ALTA"
69
+ elif confidence >= config['MODERADA']:
70
+ return "MODERADA"
71
+ else:
72
+ return "BAIXA"
73
+
74
+ @staticmethod
75
+ def generate_confidence_bar(confidence: int) -> str:
76
+ """Gera barra visual de confiança."""
77
+ filled_bars = int(confidence / 10)
78
+ empty_bars = 10 - filled_bars
79
+ return "█" * filled_bars + "░" * empty_bars
80
+
81
+ @staticmethod
82
+ def is_high_confidence(confidence: int) -> bool:
83
+ """Verifica se confiança é alta."""
84
+ return confidence >= TradingConfig.CONFIDENCE_LEVELS['ALTA']
85
+
86
+
87
+ class ActionUtils:
88
+ """Utilitários para manipulação de ações de trading."""
89
+
90
+ @staticmethod
91
+ def get_action_emojis(action: str) -> Dict[str, str]:
92
+ """Retorna emojis para ação específica."""
93
+ return UIConfig.ACTION_EMOJIS.get(action, {
94
+ 'main': '⚪',
95
+ 'action': '❓'
96
+ })
97
+
98
+ @staticmethod
99
+ def get_action_color(action: str) -> str:
100
+ """Retorna cor para ação específica."""
101
+ return UIConfig.ACTION_COLORS.get(action, 'cinza')
102
+
103
+ @staticmethod
104
+ def get_trading_direction(action: str) -> str:
105
+ """Retorna direção de trading para ação."""
106
+ return UIConfig.TRADING_DIRECTIONS.get(action, 'INDEFINIDO')
107
+
108
+
109
+ class SentimentUtils:
110
+ """Utilitários para manipulação de sentimento."""
111
+
112
+ @staticmethod
113
+ def get_sentiment_emoji(sentiment_label: str) -> str:
114
+ """Retorna emoji para sentimento."""
115
+ return UIConfig.SENTIMENT_EMOJIS.get(sentiment_label, '😐💛')
116
+
117
+ @staticmethod
118
+ def normalize_sentiment_label(label: str) -> str:
119
+ """Normaliza label de sentimento."""
120
+ label_upper = label.upper()
121
+ valid_labels = ['POSITIVO', 'NEGATIVO', 'NEUTRO']
122
+
123
+ if label_upper in valid_labels:
124
+ return label_upper
125
+
126
+ # Mapeamento de labels alternativos
127
+ label_mapping = {
128
+ 'POSITIVE': 'POSITIVO',
129
+ 'NEGATIVE': 'NEGATIVO',
130
+ 'NEUTRAL': 'NEUTRO',
131
+ 'POS': 'POSITIVO',
132
+ 'NEG': 'NEGATIVO',
133
+ 'NEU': 'NEUTRO'
134
+ }
135
+
136
+ return label_mapping.get(label_upper, 'NEUTRO')
137
+
138
+
139
+ class ValidationUtils:
140
+ """Utilitários para validação de dados."""
141
+
142
+ @staticmethod
143
+ def validate_market_data(data: Dict[str, Any]) -> bool:
144
+ """Valida dados de mercado."""
145
+ required_fields = ['price', 'variation', 'rsi', 'ema_trend', 'bb_position', 'volume']
146
+
147
+ # Verificar se todos os campos obrigatórios estão presentes
148
+ for field in required_fields:
149
+ if field not in data:
150
+ return False
151
+
152
+ # Validar tipos e valores
153
+ try:
154
+ price = float(data['price'])
155
+ variation = float(data['variation'])
156
+ rsi = int(data['rsi'])
157
+ volume = float(data['volume'])
158
+
159
+ # Validar ranges
160
+ if price < 0 or not (0 <= rsi <= 100) or volume < 0:
161
+ return False
162
+
163
+ # Validar strings
164
+ valid_ema_trends = ['ALTA', 'BAIXA', 'NEUTRO']
165
+ valid_bb_positions = ['DENTRO', 'SOBRE', 'ABAIXO', 'ACIMA']
166
+
167
+ if (data['ema_trend'] not in valid_ema_trends or
168
+ data['bb_position'] not in valid_bb_positions):
169
+ return False
170
+
171
+ return True
172
+
173
+ except (ValueError, TypeError):
174
+ return False
175
+
176
+ @staticmethod
177
+ def validate_confidence_score(score: int) -> int:
178
+ """Valida e normaliza pontuação de confiança."""
179
+ return max(ScoringConfig.MIN_CONFIDENCE,
180
+ min(ScoringConfig.MAX_CONFIDENCE, score))
181
+
182
+ @staticmethod
183
+ def validate_text_input(text: str) -> bool:
184
+ """Valida entrada de texto."""
185
+ if not text or not isinstance(text, str):
186
+ return False
187
+
188
+ # Verificar se não é apenas espaços em branco
189
+ if not text.strip():
190
+ return False
191
+
192
+ # Verificar tamanho mínimo
193
+ if len(text.strip()) < 3:
194
+ return False
195
+
196
+ return True
197
+
198
+
199
+ class FormatUtils:
200
+ """Utilitários para formatação de texto e dados."""
201
+
202
+ @staticmethod
203
+ def format_signal_list(signals: list) -> str:
204
+ """Formata lista de sinais para exibição."""
205
+ if not signals:
206
+ return "Nenhum sinal detectado"
207
+
208
+ formatted_signals = []
209
+ for i, signal in enumerate(signals[:5], 1): # Máximo 5 sinais
210
+ if hasattr(signal, 'description'):
211
+ formatted_signals.append(f"{i}. {signal.description}")
212
+ else:
213
+ formatted_signals.append(f"{i}. {str(signal)}")
214
+
215
+ return "\n".join(formatted_signals)
216
+
217
+ @staticmethod
218
+ def format_market_summary(market_data: Dict[str, Any]) -> str:
219
+ """Formata resumo dos dados de mercado."""
220
+ price = NumberUtils.format_price(market_data.get('price', 0))
221
+ variation = NumberUtils.format_percentage(market_data.get('variation', 0))
222
+ volume = NumberUtils.format_volume(market_data.get('volume', 0))
223
+
224
+ return f"""• **Preço:** {price}
225
+ • **Variação:** {variation}
226
+ • **RSI:** {market_data.get('rsi', 'N/A')}
227
+ • **EMA:** {market_data.get('ema_trend', 'N/A')}
228
+ • **Bollinger:** {market_data.get('bb_position', 'N/A')}
229
+ • **Volume:** {volume}"""
230
+
231
+ @staticmethod
232
+ def format_trading_recommendations(action: str, price: float) -> str:
233
+ """Formata recomendações de trading."""
234
+ if action == 'COMPRAR':
235
+ stop_loss = price * (1 - TradingConfig.STOP_LOSS_PERCENTAGE)
236
+ take_profit = price * (1 + TradingConfig.TAKE_PROFIT_PERCENTAGE)
237
+
238
+ return f"""• **Stop Loss:** -{NumberUtils.calculate_points_from_percentage(price, TradingConfig.STOP_LOSS_PERCENTAGE * 100):.0f} pts ({TradingConfig.STOP_LOSS_PERCENTAGE * 100:.2f}%)
239
+ • **Take Profit:** +{NumberUtils.calculate_points_from_percentage(price, TradingConfig.TAKE_PROFIT_PERCENTAGE * 100):.0f} pts ({TradingConfig.TAKE_PROFIT_PERCENTAGE * 100:.2f}%)
240
+ • **Timeframe:** {'/'.join(TradingConfig.SCALPING_TIMEFRAMES)}
241
+ • **Risk/Reward:** 1:{TradingConfig.RISK_REWARD_RATIO}"""
242
+
243
+ elif action == 'VENDER':
244
+ stop_loss = price * (1 + TradingConfig.STOP_LOSS_PERCENTAGE)
245
+ take_profit = price * (1 - TradingConfig.TAKE_PROFIT_PERCENTAGE)
246
+
247
+ return f"""• **Stop Loss:** +{NumberUtils.calculate_points_from_percentage(price, TradingConfig.STOP_LOSS_PERCENTAGE * 100):.0f} pts ({TradingConfig.STOP_LOSS_PERCENTAGE * 100:.2f}%)
248
+ • **Take Profit:** -{NumberUtils.calculate_points_from_percentage(price, TradingConfig.TAKE_PROFIT_PERCENTAGE * 100):.0f} pts ({TradingConfig.TAKE_PROFIT_PERCENTAGE * 100:.2f}%)
249
+ • **Timeframe:** {'/'.join(TradingConfig.SCALPING_TIMEFRAMES)}
250
+ • **Risk/Reward:** 1:{TradingConfig.RISK_REWARD_RATIO}"""
251
+
252
+ else:
253
+ return """• **Aguardar:** Setup mais definido
254
+ • **Monitorar:** Rompimentos de suporte/resistência
255
+ • **Observar:** Confluência de sinais técnicos"""
256
+
257
+
258
+ class LogUtils:
259
+ """Utilitários para logging e debug."""
260
+
261
+ @staticmethod
262
+ def log_analysis_result(analysis_result: Dict[str, Any]) -> None:
263
+ """Registra resultado de análise para debug."""
264
+ timestamp = DateTimeUtils.get_current_datetime()
265
+ action = analysis_result.get('action', 'UNKNOWN')
266
+ confidence = analysis_result.get('confidence', 0)
267
+
268
+ print(f"[{timestamp}] Análise: {action} (Confiança: {confidence}%)")
269
+
270
+ @staticmethod
271
+ def log_error(error_message: str, context: str = "") -> None:
272
+ """Registra erro com contexto."""
273
+ timestamp = DateTimeUtils.get_current_datetime()
274
+ context_str = f" [{context}]" if context else ""
275
+ print(f"[{timestamp}] ERRO{context_str}: {error_message}")
276
+
277
+ @staticmethod
278
+ def log_model_status(model_info: Dict[str, Any]) -> None:
279
+ """Registra status do modelo de IA."""
280
+ timestamp = DateTimeUtils.get_current_datetime()
281
+ status = "ATIVO" if model_info.get('available', False) else "INATIVO"
282
+ model_name = model_info.get('description', 'Desconhecido')
283
+
284
+ print(f"[{timestamp}] Modelo IA: {status} - {model_name}")
285
+
286
+
287
+ class DataExportUtils:
288
+ """Utilitários para exportação de dados."""
289
+
290
+ @staticmethod
291
+ def export_analysis_to_json(analysis_result: Dict[str, Any]) -> str:
292
+ """Exporta resultado de análise para JSON."""
293
+ # Preparar dados para serialização
294
+ export_data = {
295
+ 'timestamp': DateTimeUtils.get_current_datetime(),
296
+ 'action': analysis_result.get('action'),
297
+ 'confidence': analysis_result.get('confidence'),
298
+ 'market_data': analysis_result.get('market_data'),
299
+ 'sentiment': analysis_result.get('sentiment')
300
+ }
301
+
302
+ # Converter objetos complexos para dicionários
303
+ if 'signals' in analysis_result:
304
+ export_data['signals'] = [
305
+ {
306
+ 'indicator': getattr(signal, 'indicator', 'unknown'),
307
+ 'signal_type': getattr(signal, 'signal_type', 'unknown'),
308
+ 'strength': getattr(signal, 'strength', 0),
309
+ 'description': getattr(signal, 'description', '')
310
+ }
311
+ for signal in analysis_result['signals']
312
+ ]
313
+
314
+ return json.dumps(export_data, indent=2, ensure_ascii=False)
315
+
316
+ @staticmethod
317
+ def create_analysis_summary(analysis_result: Dict[str, Any]) -> Dict[str, Any]:
318
+ """Cria resumo da análise para relatórios."""
319
+ return {
320
+ 'timestamp': DateTimeUtils.get_current_datetime(),
321
+ 'action': analysis_result.get('action', 'UNKNOWN'),
322
+ 'confidence': analysis_result.get('confidence', 0),
323
+ 'confidence_level': ConfidenceUtils.get_confidence_level(
324
+ analysis_result.get('confidence', 0)
325
+ ),
326
+ 'signals_count': len(analysis_result.get('signals', [])),
327
+ 'sentiment_label': analysis_result.get('sentiment', {}).get('label', 'NEUTRO'),
328
+ 'market_price': analysis_result.get('market_data', {}).get('price', 0),
329
+ 'market_rsi': analysis_result.get('market_data', {}).get('rsi', 50)
330
+ }