# -*- coding: utf-8 -*-
"""
ROBÔ DE BALANCEAMENTO DE PORTFÓLIO
Permite operação simultânea em múltiplas moedas com rebalanceamento automático
Baseado na estrutura do robo_trade.py
"""

import ccxt
import pandas as pd
import time
import logging
from logging.handlers import RotatingFileHandler
import json
import locale
import os
import traceback
from datetime import datetime, timedelta
import pytz
from colorama import init, Fore, Style

init()  # Inicializa suporte a ANSI em Windows

# ===================== CONFIGURAÇÕES INICIAIS =====================

def carregar_configuracoes(filepath):
    """Carrega configurações do arquivo JSON"""
    with open(filepath, 'r', encoding='utf-8') as file:
        return json.load(file)

def salvar_estado_portfolio(filepath, estado):
    """Salva o estado atual do portfólio"""
    with open(filepath, 'w', encoding='utf-8') as file:
        json.dump(estado, file, indent=2, ensure_ascii=False)

def carregar_estado_portfolio(filepath):
    """Carrega o estado do portfólio se existir"""
    try:
        if os.path.exists(filepath):
            with open(filepath, 'r', encoding='utf-8') as file:
                data = json.load(file)
                if data:
                    return data
                else:
                    logging.warning(f"Arquivo {filepath} está vazio")
                    return None
        return None
    except json.JSONDecodeError as e:
        logging.error(f"Erro ao decodificar JSON em {filepath}: {e}")
        return None
    except Exception as e:
        logging.error(f"Erro ao carregar estado: {e}")
        return None

# Configurar locale e timezone
try:
    locale.setlocale(locale.LC_ALL, 'pt_BR.UTF-8')
except locale.Error:
    try:
        locale.setlocale(locale.LC_ALL, 'Portuguese_Brazil.1252')
    except locale.Error:
        pass

# Carregar configurações
config = carregar_configuracoes('configuracoes_balanceamento.json')

# ===================== VARIÁVEIS GLOBAIS =====================

API_KEY = config.get("api_key", "")
API_SECRET = config.get("api_secret", "")
API_KEY_TESTNET = config.get("api_key_testnet", "")
API_SECRET_TESTNET = config.get("api_secret_testnet", "")
TESTNET = config.get("testnet", False)
MOEDA_BASE = config.get("moeda_base", "USDT")
CAPITAL_INICIAL = float(config.get("capital_inicial", 1000))
TEMPO_CICLO = int(config.get("tempo_ciclo", 5))
TEMPO_REFRESH = int(config.get("tempo_refresh_tela", 5))
TIMEFRAME = config.get("timeframe", "5m")
VARIACAO_BALANCEAMENTO = float(config.get("variacao_balanceamento", 2.0))
COMISSAO = float(config.get("comissao", 0.1)) / 100
SALDO_MINIMO = float(config.get("saldo_minimo", 10))
VALOR_MINIMO_OPERACAO = 5.0  # Valor mínimo de operação na Binance em USD/USDT
COMPRAR_IMEDIATAMENTE = config.get("comprar_imediatamente", False)
PORTFOLIO_CONFIG = config.get("portfolio", [])

# Paths
LOG_FILENAME = 'log_balanceamento.log'
PORTFOLIO_STATE_FILE = 'portfolio_estado.json'

# Configurar logging com rotação automática
# Rotaciona quando o arquivo atinge 50MB, mantendo 3 backups (log.1, log.2, log.3)
logger = logging.getLogger()
logger.setLevel(logging.INFO)

# Remover handlers padrão se existirem
for handler in logger.handlers[:]:
    logger.removeHandler(handler)

# Criar RotatingFileHandler
rotating_handler = RotatingFileHandler(
    LOG_FILENAME,
    maxBytes=20 * 1024 * 1024,  # 20 MB
    backupCount=3,  # Manter 3 backups
    encoding='utf-8'
)
rotating_handler.setLevel(logging.INFO)

# Criar formatter
formatter = logging.Formatter('%(message)s')
rotating_handler.setFormatter(formatter)

# Adicionar handler ao logger
logger.addHandler(rotating_handler)

timezone = pytz.timezone("America/Sao_Paulo")

# Função para sincronizar timestamp com servidor Binance
def sincronizar_timestamp_binance():
    """Sincroniza o timestamp com o servidor da Binance para evitar erros -1021"""
    try:
        # Criar exchange temporário sem credenciais para teste
        public_exchange = ccxt.binance()
        server_time = public_exchange.fetch_time()
        local_time = int(time.time() * 1000)
        time_offset = server_time - local_time
        
        if abs(time_offset) > 1000:  # Mais de 1 segundo de diferença
            logging.warning(f"Desincronização de relógio detectada: {abs(time_offset)}ms")
            print(Fore.YELLOW + f"⚠ AVISO: Relógio do PC está {time_offset/1000:.1f}s {'adiantado' if time_offset > 0 else 'atrasado'}" + Style.RESET_ALL)
        
        return time_offset
    except Exception as e:
        logging.warning(f"Não foi possível sincronizar timestamp: {e}")
        return 0

# Sincronizar relógio
time_offset = sincronizar_timestamp_binance()

# Selecionar chaves corretas baseado no modo
if TESTNET:
    chave_api = API_KEY_TESTNET
    chave_secreta = API_SECRET_TESTNET
else:
    chave_api = API_KEY
    chave_secreta = API_SECRET

# Conectar à exchange
exchange = ccxt.binance({
    'apiKey': chave_api,
    'secret': chave_secreta,
    'enableRateLimit': True,
    'options': {
        'defaultType': 'spot',
        'recvWindow': 20000,  # Janela de recepção 20 segundos para tolerância maior
        'timeDifference': time_offset,  # Usar timeDifference para sincronização
    },
})

if TESTNET:
    exchange.set_sandbox_mode(True)
    logging.info("Modo Testnet: ATIVADO (usando chaves testnet)")
    print("Modo Testnet: ATIVADO (usando chaves testnet)")
else:
    logging.info("Modo Testnet: DESATIVADO")
    print("Modo Testnet: DESATIVADO")

if COMPRAR_IMEDIATAMENTE:
    logging.info("Compra Imediata: ATIVADA (tolerância 10%)")
    print("Compra Imediata: ATIVADA (tolerância 10%)")
else:
    logging.info("Compra Imediata: DESATIVADA (tolerância 2%)")
    print("Compra Imediata: DESATIVADA (tolerância 2%)")

logging.info(f"Valor Mínimo de Operação: ${VALOR_MINIMO_OPERACAO:.2f} (Binance)")
print(f"Valor Mínimo de Operação: ${VALOR_MINIMO_OPERACAO:.2f} (Binance)")

try:
    exchange.load_markets()
    logging.info("✓ Exchange conectada com sucesso")
    print(Fore.GREEN + "✓ Mercados carregados (Exchange pronta)" + Style.RESET_ALL)
except Exception as e:
    logging.error(f"Falha ao configurar exchange: {e}")
    print(f"Falha ao configurar exchange: {e}")

# ===================== FUNÇÕES DE UTILIDADE =====================

def log_info(mensagem):
    """Log e print simultâneos"""
    timestamp = datetime.now(timezone).strftime("%d/%m/%Y %H:%M:%S")
    msg_completa = f"[{timestamp}] {mensagem}"
    logging.info(msg_completa)
    print(msg_completa)

def log_erro(mensagem):
    """Log e print de erro"""
    timestamp = datetime.now(timezone).strftime("%d/%m/%Y %H:%M:%S")
    msg_completa = f"[{timestamp}] ERRO: {mensagem}"
    logging.error(msg_completa)
    print(Fore.RED + msg_completa + Style.RESET_ALL)

def log_aviso(mensagem):
    """Log e print de aviso"""
    timestamp = datetime.now(timezone).strftime("%d/%m/%Y %H:%M:%S")
    msg_completa = f"[{timestamp}] AVISO: {mensagem}"
    logging.warning(msg_completa)
    print(Fore.YELLOW + msg_completa + Style.RESET_ALL)

def log_sucesso(mensagem):
    """Log e print de sucesso"""
    timestamp = datetime.now(timezone).strftime("%d/%m/%Y %H:%M:%S")
    msg_completa = f"[{timestamp}] ✓ {mensagem}"
    logging.info(msg_completa)
    print(Fore.GREEN + msg_completa + Style.RESET_ALL)

def obter_preco_atual(symbol):
    """Obtém o preço atual de um símbolo com retry automático"""
    max_tentativas = 3
    for tentativa in range(max_tentativas):
        try:
            ticker = exchange.fetch_ticker(symbol)
            price = ticker.get('last') or ticker.get('close')
            return float(price) if price else None
        except Exception as e:
            erro_str = str(e)
            # Verificar se é erro de timestamp (-1021)
            if "-1021" in erro_str or "Timestamp" in erro_str:
                if tentativa < max_tentativas - 1:
                    log_aviso(f"Erro de timestamp ao obter {symbol}, resincronizando... (tentativa {tentativa + 1}/{max_tentativas})")
                    global time_offset
                    time_offset = sincronizar_timestamp_binance()
                    exchange.options['timeDifference'] = time_offset  # Usar timeDifference
                    time.sleep(2)  # Esperar 2 segundos antes de retry
                    continue
            
            if tentativa == max_tentativas - 1:
                log_erro(f"Erro ao obter preço de {symbol}: {e}")
            return None

def obter_saldo(ativo):
    """Obtém saldo disponível de um ativo com retry automático"""
    max_tentativas = 3
    for tentativa in range(max_tentativas):
        try:
            balance = exchange.fetch_balance()
            free = balance.get('free', {})
            return float(free.get(ativo, 0))
        except Exception as e:
            erro_str = str(e)
            # Verificar se é erro de timestamp (-1021)
            if "-1021" in erro_str or "Timestamp" in erro_str:
                if tentativa < max_tentativas - 1:
                    log_aviso(f"Erro de timestamp ao obter saldo de {ativo}, resincronizando... (tentativa {tentativa + 1}/{max_tentativas})")
                    global time_offset
                    time_offset = sincronizar_timestamp_binance()
                    exchange.options['timeDifference'] = time_offset  # Usar timeDifference
                    time.sleep(2)  # Esperar 2 segundos antes de retry
                    continue
            
            if tentativa == max_tentativas - 1:
                log_erro(f"Erro ao obter saldo de {ativo}: {e}")
            return 0.0

def obter_saldos_portfolio(moedas):
    """Obtém saldos de todas as moedas do portfólio"""
    saldos = {}
    for moeda in moedas:
        saldos[moeda] = obter_saldo(moeda)
    saldos[MOEDA_BASE] = obter_saldo(MOEDA_BASE)
    return saldos

def verificar_par_disponivel(symbol):
    """
    Verifica se um par de trading está disponível e funcionando no testnet
    
    Returns:
        tuple: (está_disponível, info_diagnóstica)
    """
    try:
        # Tentar carregar informações do mercado do par
        if symbol not in exchange.symbols:
            return False, f"Par {symbol} não está na lista de símbolos disponíveis no testnet"
        
        # Tentar obter ticker (informações de preço)
        ticker = exchange.fetch_ticker(symbol)
        last_price = ticker.get('last') or ticker.get('close')
        volume = ticker.get('baseVolume')
        
        if not last_price or last_price <= 0:
            return False, f"Par {symbol} não tem preço válido (último: {last_price})"
        
        if volume is None or volume == 0:
            return False, f"Par {symbol} tem volume zero - sem liquidez no testnet"
        
        return True, f"Par {symbol} OK - Preço: {last_price:.8f}, Volume: {volume:.8f}"
        
    except Exception as e:
        return False, f"Erro ao verificar par {symbol}: {str(e)}"

def obter_valor_portfolio(moedas, saldos, precos):
    """Calcula o valor total do portfólio em USDT"""
    valor_total = saldos.get(MOEDA_BASE, 0)
    for moeda in moedas:
        quantidade = saldos.get(moeda, 0)
        preco = precos.get(moeda, 0)
        if quantidade > 0 and preco:
            valor_total += quantidade * preco
    return valor_total

def calcular_percentuais_atuais(moedas, saldos, precos, valor_total):
    """Calcula os percentuais atuais de cada moeda no portfólio"""
    percentuais = {}
    if valor_total <= 0:
        for moeda in moedas:
            percentuais[moeda] = 0
        return percentuais
    
    for moeda in moedas:
        quantidade = saldos.get(moeda, 0)
        preco = precos.get(moeda, 0)
        if quantidade > 0 and preco:
            valor_moeda = quantidade * preco
            percentuais[moeda] = (valor_moeda / valor_total) * 100
        else:
            percentuais[moeda] = 0
    
    return percentuais

def remover_outliers_iqr(precos):
    """
    Remove outliers usando o método Interquartile Range (IQR)
    
    Args:
        precos: lista de preços
    
    Returns:
        tuple: (preços filtrados sem outliers, índices dos outliers)
    """
    if len(precos) < 4:
        return precos, []
    
    # Calcular Q1 e Q3
    sorted_precos = sorted(precos)
    n = len(sorted_precos)
    q1_idx = n // 4
    q3_idx = 3 * n // 4
    
    q1 = sorted_precos[q1_idx]
    q3 = sorted_precos[q3_idx]
    iqr = q3 - q1
    
    # Definir limites (1.5 × IQR é o padrão)
    limite_inferior = q1 - 1.5 * iqr
    limite_superior = q3 + 1.5 * iqr
    
    # Filtrar outliers
    precos_filtrados = []
    indices_outliers = []
    
    for idx, preco in enumerate(precos):
        if limite_inferior <= preco <= limite_superior:
            precos_filtrados.append(preco)
        else:
            indices_outliers.append(idx)
    
    return precos_filtrados, indices_outliers

def obter_melhores_precos_5dias(moedas):
    """
    DESCONTINUADO: Use obter_melhores_precos_30dias() em vez disso.
    Mantido para compatibilidade com versões antigas.
    """
    return obter_melhores_precos_30dias(moedas)

def obter_melhores_precos_30dias(moedas):
    """
    Busca os melhores preços (máxima e mínima) dos últimos 30 dias para cada moeda
    usando dados OHLCV (Open, High, Low, Close, Volume) da exchange
    Remove outliers para análise de padrões de horário mais confiáveis
    
    Retorna análise consolidada sem detalhe dia a dia para maior confiabilidade.
    Com 30 dias (720 candles), a confiança dos padrões é altamente confiável.
    
    Returns:
        dict: {
            'moeda': {
                'melhor_hora_compra': { hora, confianca%, ocorrencias },
                'melhor_hora_venda': { hora, confianca%, ocorrencias },
                'analise': {
                    'total_registros': int (candles coletados nos 30 dias),
                    'registros_validos': int (após remover outliers),
                    'total_outliers': int,
                    'percentual_outliers': float (%)
                },
                'timestamp': str (ISO 8601)
            }
        }
    """
    melhores_precos = {}
    
    try:
        # Buscar 30 dias em candles de 1 hora
        # 30 dias × 24 horas = 720 candles
        timeframe = '1h'
        
        for moeda in moedas:
            try:
                symbol = f"{moeda}/{MOEDA_BASE}"
                
                # Buscar candles dos últimos 30 dias
                candles = exchange.fetch_ohlcv(symbol, timeframe, limit=721)
                
                if not candles or len(candles) < 500:  # Pelo menos 21 dias
                    log_aviso(f"Candles insuficientes para {moeda} (precisa 500, tem {len(candles)})")
                    continue
                
                # Filtrar apenas os últimos candles disponíveis (30 dias = 720 candles)
                candles = candles[-721:] if len(candles) >= 721 else candles
                
                total_registros = len(candles)
                
                # Extrair todos os preços altos e baixos para análise consolidada
                highs = [candle[2] for candle in candles]
                lows = [candle[3] for candle in candles]
                
                # Remover outliers usando IQR
                highs_filtrados, outliers_high = remover_outliers_iqr(highs)
                lows_filtrados, outliers_low = remover_outliers_iqr(lows)
                
                # Contar outliers únicos
                indices_outliers = set(outliers_high) | set(outliers_low)
                total_outliers = len(indices_outliers)
                registros_validos = total_registros - total_outliers
                percentual_outliers = (total_outliers / total_registros * 100) if total_registros > 0 else 0
                
                # Dados para análise de padrões (apenas horários de mín/máx válidos)
                horas_minimas = []  # Horários quando o preço foi mínimo (sem outliers)
                horas_maximas = []  # Horários quando o preço foi máximo (sem outliers)
                
                # Encontrar mínimo global e máximo global (sem outliers)
                min_global = min(lows_filtrados) if lows_filtrados else 0
                max_global = max(highs_filtrados) if highs_filtrados else 0
                
                # Coletar horários de cada mínimo/máximo
                for idx, candle in enumerate(candles):
                    if idx not in indices_outliers:
                        # Extrair horário
                        timestamp = candle[0]
                        dt = datetime.fromtimestamp(timestamp / 1000, tz=timezone)
                        hora = dt.strftime('%H:%M')
                        
                        # Se é mínimo (dentro dos dados válidos)
                        if candle[3] == min_global:
                            horas_minimas.append(hora)
                        
                        # Se é máximo (dentro dos dados válidos)
                        if candle[2] == max_global:
                            horas_maximas.append(hora)
                
                # Se ainda não tem muitos dados, coletar todas as horas de mín/máx
                if len(horas_minimas) < 3 or len(horas_maximas) < 3:
                    horas_minimas = []
                    horas_maximas = []
                    
                    for idx, candle in enumerate(candles):
                        if idx not in indices_outliers:
                            timestamp = candle[0]
                            dt = datetime.fromtimestamp(timestamp / 1000, tz=timezone)
                            hora = dt.strftime('%H:%M')
                            
                            # Adicionar horários de pontos baixos
                            if candle[3] <= min_global * 1.01:  # Dentro de 1% do mínimo
                                horas_minimas.append(hora)
                            
                            # Adicionar horários de pontos altos
                            if candle[2] >= max_global * 0.99:  # Dentro de 1% do máximo
                                horas_maximas.append(hora)
                
                if not horas_minimas or not horas_maximas:
                    log_aviso(f"{moeda}: Sem dados válidos após filtragem")
                    continue
                
                # Analisar padrões de horário com Counter
                def encontrar_hora_mais_frequente(horas_lista):
                    """Encontra a hora mais frequente com detalhes"""
                    if not horas_lista:
                        return None, 0, 0
                    
                    from collections import Counter
                    conta_horas = Counter(horas_lista)
                    hora_mais_freq, freq = conta_horas.most_common(1)[0]
                    confianca = (freq / len(horas_lista)) * 100
                    
                    return hora_mais_freq, freq, confianca
                
                melhor_compra_hora, melhor_compra_freq, melhor_compra_conf = encontrar_hora_mais_frequente(horas_minimas)
                melhor_venda_hora, melhor_venda_freq, melhor_venda_conf = encontrar_hora_mais_frequente(horas_maximas)
                
                # Montar dados consolidados
                dados_moeda = {
                    'melhor_hora_compra': {
                        'hora': melhor_compra_hora or 'N/A',
                        'confianca': round(melhor_compra_conf, 1) if melhor_compra_conf else 0,
                        'ocorrencias': melhor_compra_freq
                    },
                    'melhor_hora_venda': {
                        'hora': melhor_venda_hora or 'N/A',
                        'confianca': round(melhor_venda_conf, 1) if melhor_venda_conf else 0,
                        'ocorrencias': melhor_venda_freq
                    },
                    'analise': {
                        'total_registros': total_registros,
                        'registros_validos': registros_validos,
                        'total_outliers': total_outliers,
                        'percentual_outliers': round(percentual_outliers, 1)
                    },
                    'timestamp': datetime.now(tz=timezone).isoformat()
                }
                
                melhores_precos[moeda] = dados_moeda
                
                # Log informativo consolidado
                log_info(f"  {moeda}:")
                log_info(f"    📊 Registros: {total_registros} coletados | {registros_validos} válidos ({100-percentual_outliers:.1f}%)")
                log_info(f"    ⚠️  Outliers: {total_outliers} removidos ({percentual_outliers:.1f}%)")
                log_info(f"    ✓ Hora COMPRA:  {dados_moeda['melhor_hora_compra']['hora']} | Confiança: {dados_moeda['melhor_hora_compra']['confianca']:.1f}% | Ocorreu {melhor_compra_freq}x")
                log_info(f"    ✓ Hora VENDA:   {dados_moeda['melhor_hora_venda']['hora']} | Confiança: {dados_moeda['melhor_hora_venda']['confianca']:.1f}% | Ocorreu {melhor_venda_freq}x")
                
            except Exception as e:
                log_aviso(f"Erro ao buscar dados de {moeda}: {e}")
                continue
        
        return melhores_precos
        
    except Exception as e:
        log_erro(f"Erro ao obter melhores preços: {e}")
        return {}

def atualizar_melhores_precos_portfolio(estado, moedas):
    """
    Atualiza o arquivo de estado do portfólio com os melhores preços dos últimos 30 dias
    Incluindo análise de padrões de horário e detecção de outliers
    """
    try:
        log_info("Atualizando dados de melhores preços dos últimos 30 dias (com análise consolidada)...")
        
        melhores_precos = obter_melhores_precos_30dias(moedas)
        
        if melhores_precos:
            estado['best_prices_30days'] = melhores_precos
            salvar_estado_portfolio(PORTFOLIO_STATE_FILE, estado)
            log_info(f"✓ Dados de melhores preços (30 dias) atualizados para {len(melhores_precos)} moedas")
        
        return estado
        
    except Exception as e:
        log_erro(f"Erro ao atualizar melhores preços: {e}")
        return estado

def executar_ordem(symbol, side, quantidade):
    """
    Executa uma ordem de compra ou venda (limit order)
    Limit orders são mais confiáveis no testnet que market orders
    """
    try:
        # Obter preço atual para usar como referência no limit order
        preco_atual = obter_preco_atual(symbol)
        
        if not preco_atual or preco_atual <= 0:
            log_erro(f"Falha ao obter preço para {symbol}. Não será possível criar limit order.")
            return None
        
        # Calcular preço limite (adiciona 1% de margem para garantir execução)
        if side == 'buy':
            preco_limite = preco_atual * 1.02  # 2% acima para compra
        else:
            preco_limite = preco_atual * 0.98  # 2% abaixo para venda
        
        log_info(f"  Criando limit order: {side} {quantidade} {symbol}")
        log_info(f"    Preço Atual: {preco_atual:.8f}")
        log_info(f"    Preço Limite: {preco_limite:.8f}")
        
        # Tentar criar limit order em vez de market order
        try:
            ordem = exchange.create_limit_order(
                symbol, 
                side, 
                quantidade, 
                preco_limite,
                {
                    'timeInForce': 'GTC',  # Good-Till-Cancelled (no time limit)
                }
            )
            timestamp = datetime.now(timezone).strftime("%d/%m/%Y %H:%M:%S")
            
            # Log detalhado da ordem
            order_id = ordem.get('id', 'N/A')
            status = ordem.get('status', 'desconhecido')
            filled = ordem.get('filled', 0)
            amount = ordem.get('amount', 0)
            
            log_info(f"  ID: {order_id} | Status: {status} | Preenchida: {filled:.8f}/{amount:.8f}")
            
            # Diagnosticar se ordem expirou ou foi rejeitada
            if status == 'expired':
                log_erro(f"  ⚠️ AVISO: Ordem expirou imediatamente (ID: {order_id})")
                log_erro(f"     Possíveis causas:")
                log_erro(f"     1. Par {symbol} pode não ter liquidez no testnet")
                log_erro(f"     2. Quantidade {quantidade:.8f} pode ser inválida")
                log_erro(f"     3. Preço {preco_limite:.8f} fora do spread")
                return None
            elif status == 'canceled':
                log_erro(f"  ⚠️ AVISO: Ordem foi cancelada pela Binance (ID: {order_id})")
                return None
            
            if side == 'sell':
                log_sucesso(f"✓ Ordem de VENDA executada: {quantidade:.8f} {symbol} em {timestamp}")
            else:
                log_sucesso(f"✓ Ordem de COMPRA executada: {quantidade:.8f} {symbol} em {timestamp}")
            
            return ordem
            
        except Exception as e_limit:
            erro_str = str(e_limit)
            log_aviso(f"  Limit order falhou: {erro_str}")
            log_aviso(f"  Tentando market order como fallback...")
            
            # Fallback para market order se limit order falhar
            try:
                ordem = exchange.create_market_order(symbol, side, quantidade)
                timestamp = datetime.now(timezone).strftime("%d/%m/%Y %H:%M:%S")
                
                order_id = ordem.get('id', 'N/A')
                status = ordem.get('status', 'desconhecido')
                filled = ordem.get('filled', 0)
                amount = ordem.get('amount', 0)
                
                log_info(f"  ID: {order_id} | Status: {status} | Preenchida: {filled:.8f}/{amount:.8f}")
                
                if status == 'expired':
                    log_erro(f"  ⚠️ Market order também expirou (ID: {order_id})")
                    log_erro(f"     Problema aparentemente com par/liquidez no testnet")
                    return None
                
                if side == 'sell':
                    log_sucesso(f"✓ Ordem de VENDA (market) executada: {quantidade:.8f} {symbol} em {timestamp}")
                else:
                    log_sucesso(f"✓ Ordem de COMPRA (market) executada: {quantidade:.8f} {symbol} em {timestamp}")
                
                return ordem
            except Exception as e_market:
                log_erro(f"  Ambas market e limit order falharam para {symbol}")
                log_erro(f"  Market error: {str(e_market)}")
                return None
                
    except Exception as e:
        log_erro(f"Falha ao executar ordem {side} para {symbol} ({quantidade:.8f}): {e}")
        log_erro(f"  Rastreamento: {traceback.format_exc()}")
        return None

def aplicar_comissao(quantidade):
    """Aplica comissão à quantidade"""
    return quantidade * (1 - COMISSAO)

def validar_valor_operacao(valor_operacao, moeda, tipo_operacao):
    """
    Valida se o valor da operação atende ao mínimo da Binance ($5 USD/USDT)
    
    Args:
        valor_operacao: Valor da operação em USDT
        moeda: Símbolo da moeda (ex: BTC)
        tipo_operacao: 'compra' ou 'venda'
    
    Returns:
        tuple: (é_válido, mensagem)
    """
    if valor_operacao < VALOR_MINIMO_OPERACAO:
        mensagem = (f"Operação de {tipo_operacao} de {moeda} BLOQUEADA: "
                  f"Valor ${valor_operacao:.2f} é inferior ao mínimo de "
                  f"${VALOR_MINIMO_OPERACAO:.2f} exigido pela Binance")
        return False, mensagem
    return True, f"Operação de {tipo_operacao} de {moeda} validada: ${valor_operacao:.2f}"

# ===================== FUNÇÕES PRINCIPAIS =====================

def inicializar_portfolio():
    """Inicializa o estado do portfólio"""
    moedas = [m['moeda'] for m in PORTFOLIO_CONFIG if m.get('ativa', True)]
    percentuais_alvo = {m['moeda']: m['percentual'] for m in PORTFOLIO_CONFIG if m.get('ativa', True)}
    precos_compra = {m['moeda']: m['preco_compra'] for m in PORTFOLIO_CONFIG if m.get('ativa', True)}
    
    estado_existente = carregar_estado_portfolio(PORTFOLIO_STATE_FILE)
    
    if estado_existente:
        log_info("Carregando estado anterior do portfólio...")
        return estado_existente, moedas
    
    # Verificar se já existe portfólio no testnet (moedas com saldo)
    saldos_reais = obter_saldos_portfolio(moedas)
    moedas_com_saldo = [m for m in moedas if saldos_reais.get(m, 0) > 0.00001]
    
    if moedas_com_saldo:
        # Portfólio já existe no testnet, pular para balanceamento
        log_aviso(f"Portfólio detectado no testnet com {len(moedas_com_saldo)} moedas: {', '.join(moedas_com_saldo)}")
        
        estado = {
            'fase': 'balanceamento',  # Já com moedas, vai direto para balanceamento
            'data_inicio': datetime.now(timezone).isoformat(),
            'capital_inicial': CAPITAL_INICIAL,
            'moedas_compradas': moedas.copy(),  # Todas já existem
            'moedas_pendentes': [],
            'percentuais_alvo': percentuais_alvo,
            'precos_compra': precos_compra,
            'historico_trades': []
        }
        log_info("✓ Fase de Balanceamento iniciada (portfólio existente)")
        return estado, moedas
    
    # Novo portfólio (sem moedas no testnet)
    estado = {
        'fase': 'construcao',
        'data_inicio': datetime.now(timezone).isoformat(),
        'capital_inicial': CAPITAL_INICIAL,
        'moedas_compradas': [],
        'moedas_pendentes': moedas.copy(),
        'percentuais_alvo': percentuais_alvo,
        'precos_compra': precos_compra,
        'historico_trades': []
    }
    
    log_info("===================== NOVO PORTFÓLIO INICIADO =====================")
    log_info(f"Capital Inicial: {CAPITAL_INICIAL} {MOEDA_BASE}")
    log_info(f"Moedas no Portfólio: {', '.join(moedas)}")
    log_info(f"Percentuais Alvo: {percentuais_alvo}")
    log_info(f"Preços de Compra (entrada): {precos_compra}")
    log_info("=" * 60)
    
    return estado, moedas

def fase_construcao_portfolio(estado, moedas):
    """
    Fase 1: Completa o portfólio comprando as moedas até os preços definidos
    """
    log_info("\n========== FASE DE CONSTRUÇÃO DO PORTFÓLIO ==========")
    
    saldos = obter_saldos_portfolio(moedas)
    precos = {m: obter_preco_atual(f"{m}/{MOEDA_BASE}") for m in moedas}
    
    # Calcular valor do portfólio baseado em saldos REAIS
    saldo_usdt = saldos.get(MOEDA_BASE, 0)
    valor_portfolio = saldo_usdt
    for moeda in moedas:
        quantidade = saldos.get(moeda, 0)
        preco = precos.get(moeda, 0)
        if quantidade > 0 and preco:
            valor_portfolio += quantidade * preco
    
    # Se saldo_usdt é 0, usar capital inicial como base de trabalho
    saldo_para_compra = saldo_usdt if saldo_usdt > 0 else CAPITAL_INICIAL
    
    log_info(f"Saldo USDT atual: {saldo_usdt:.2f}")
    if saldo_usdt == 0 and CAPITAL_INICIAL > 0:
        log_aviso(f"Testnet sem USDT. Usando capital inicial como referência: {CAPITAL_INICIAL:.2f}")
    log_info(f"Valor Total do Portfólio: {valor_portfolio:.2f} {MOEDA_BASE}")
    
    moedas_compradas = estado.get('moedas_compradas', [])
    moedas_pendentes = []
    percentuais_alvo = estado['percentuais_alvo']
    precos_compra = estado['precos_compra']
    
    for moeda in moedas:
        preco_atual = precos.get(moeda, 0)
        preco_alvo = precos_compra.get(moeda, 0)  # Preço limite de entrada desejado
        quantidade_atual = saldos.get(moeda, 0)
        
        # Moeda já possui saldo, não precisa comprar
        if quantidade_atual > 0.00001:
            log_info(f"{moeda}: Já possui {quantidade_atual:.8f} em carteira")
            moedas_compradas.append(moeda)
            continue
        
        if moeda not in moedas_compradas:
            # Moeda não foi comprada ainda
            moedas_pendentes.append(moeda)
            
            # Lógica de compra melhorada
            if COMPRAR_IMEDIATAMENTE:
                # Compra imediata: compra agora mesmo com preço um pouco acima do alvo
                if preco_alvo > 0:
                    diferenca_percentual = ((preco_atual - preco_alvo) / preco_alvo * 100)
                    condicao_compra = diferenca_percentual <= 50  # Aceita até 50% acima do alvo em compra imediata
                else:
                    condicao_compra = True  # Se não há alvo definido, compra qualquer preço
            else:
                # Compra conservadora: só compra se preço está abaixo do alvo (ou muito perto)
                tolerancia = 0.02  # 2% de tolerância
                if preco_alvo > 0:
                    condicao_compra = preco_atual <= preco_alvo * (1 + tolerancia)
                else:
                    condicao_compra = False  # Sem preço alvo definido, não compra
            
            if preco_atual and condicao_compra:
                # Preço está bom para compra
                percentual_alvo = percentuais_alvo.get(moeda, 0) / 100
                valor_compra = valor_portfolio * percentual_alvo
                quantidade = valor_compra / preco_atual * (1 - COMISSAO)
                
                # Usar saldo real para compra, se zero usa capital inicial como referência
                saldo_disponivel = saldo_usdt if saldo_usdt > 0 else CAPITAL_INICIAL
                
                # Validar valor mínimo de operação na Binance
                eh_valido, msg_validacao = validar_valor_operacao(valor_compra, moeda, 'compra')
                
                if eh_valido and valor_compra >= SALDO_MINIMO and saldo_disponivel >= valor_compra:
                    log_aviso(f"\n>>> Comprando {moeda} <<<")
                    log_info(f"    Preço Atual: {preco_atual:.8f}")
                    log_info(f"    Preço Alvo: {preco_alvo:.8f}")
                    log_info(f"    Valor: {valor_compra:.2f} {MOEDA_BASE}")
                    log_info(f"    Quantidade: {quantidade:.8f}")
                    
                    # Verificar se o par está disponível ANTES de tentar comprar
                    symbol = f"{moeda}/{MOEDA_BASE}"
                    par_disponivel, info_par = verificar_par_disponivel(symbol)
                    
                    if not par_disponivel:
                        log_erro(f"  ⚠️ Par {symbol} não está disponível no testnet!")
                        log_erro(f"     {info_par}")
                        log_aviso(f"  Pulando compra de {moeda} - par indisponível")
                        continue
                    
                    log_info(f"  ✓ {info_par}")
                    
                    # Obter saldo ANTES da compra
                    saldo_antes = obter_saldo(moeda)
                    
                    ordem = executar_ordem(symbol, 'buy', quantidade)
                    
                    if ordem:
                        # Aguardar um pouco para a Binance processar
                        time.sleep(1)
                        
                        # Obter saldo DEPOIS da compra
                        saldo_depois = obter_saldo(moeda)
                        
                        # Se saldo não mudou, aguardar mais e tentar novamente
                        if saldo_depois == saldo_antes:
                            log_aviso(f"  Saldo não mudou na primeira verificação, aguardando mais 2s...")
                            time.sleep(2)
                            saldo_depois = obter_saldo(moeda)
                        
                        # Verificar se o saldo realmente aumentou
                        diferenca_saldo = saldo_depois - saldo_antes
                        
                        if diferenca_saldo > 0:
                            log_sucesso(f"✓ Compra verificada: Saldo aumentou {diferenca_saldo:.8f} {moeda}")
                            moedas_compradas.append(moeda)
                            estado['moedas_compradas'] = moedas_compradas
                            estado['historico_trades'].append({
                                'timestamp': datetime.now(timezone).isoformat(),
                                'moeda': moeda,
                                'tipo': 'compra_inicial',
                                'preco': preco_atual,
                                'quantidade': quantidade,
                                'valor': valor_compra
                            })
                        else:
                            log_erro(f"✗ FALHA: Compra de {moeda} não confirmada! Saldo não aumentou.")
                            log_erro(f"  Antes: {saldo_antes:.8f} {moeda} | Depois: {saldo_depois:.8f} {moeda}")
                            log_erro(f"  ⚠ AVISO: A Binance aceitou a ordem mas não creditou o saldo!")
                            log_erro(f"  Possível causa: Problema no par {symbol} no testnet")
                        
                        time.sleep(1)
                elif not eh_valido:
                    log_aviso(msg_validacao)
                else:
                    log_aviso(f"Saldo insuficiente para comprar {moeda} (precisa {valor_compra:.2f}, tem {saldo_disponivel:.2f})")
            else:
                # Mostrar por que está aguardando
                if preco_alvo > 0:
                    diferenca_pct = ((preco_atual - preco_alvo) / preco_alvo * 100)
                    if COMPRAR_IMEDIATAMENTE:
                        mensagem = f"{moeda}: Preço muito alto para compra imediata ({diferenca_pct:.1f}% acima). Esperando melhora..."
                    else:
                        mensagem = f"{moeda}: Aguardando preço melhor (Atual: {preco_atual:.8f} | Alvo: {preco_alvo:.8f} | Diferença: +{diferenca_pct:.1f}%)"
                else:
                    mensagem = f"{moeda}: Preço alvo não definido na configuração"
                log_info(mensagem)
        else:
            moedas_compradas.append(moeda)
    
    estado['moedas_pendentes'] = moedas_pendentes
    estado['moedas_compradas'] = moedas_compradas
    
    # Verificar se portfólio está completo
    if len(moedas_pendentes) == 0:
        log_sucesso("PORTFÓLIO COMPLETO! Iniciando fase de balanceamento...")
        estado['fase'] = 'balanceamento'
        estado['data_balanceamento_inicio'] = datetime.now(timezone).isoformat()
    
    salvar_estado_portfolio(PORTFOLIO_STATE_FILE, estado)
    return estado

def fase_balanceamento_portfolio(estado, moedas):
    """
    Fase 2: Balanceia o portfólio mantendo os percentuais definidos
    Vende moedas acima do percentual e compra moedas abaixo
    """
    log_info("\n========== FASE DE BALANCEAMENTO DO PORTFÓLIO ==========")
    
    saldos = obter_saldos_portfolio(moedas)
    precos = {m: obter_preco_atual(f"{m}/{MOEDA_BASE}") for m in moedas}
    valor_portfolio = obter_valor_portfolio(moedas, saldos, precos)
    
    log_info(f"Valor Total do Portfólio: {valor_portfolio:.2f} {MOEDA_BASE}")
    
    # Calcular percentuais atuais
    percentuais_atuais = calcular_percentuais_atuais(moedas, saldos, precos, valor_portfolio)
    percentuais_alvo = estado['percentuais_alvo']
    
    log_info("\n--- Análise de Percentuais ---")
    moedas_venda = []
    moedas_compra = []
    
    for moeda in moedas:
        pct_atual = percentuais_atuais.get(moeda, 0)
        pct_alvo = percentuais_alvo.get(moeda, 0)
        diferenca = pct_atual - pct_alvo
        
        log_info(f"{moeda}: {pct_atual:.2f}% (Alvo: {pct_alvo:.2f}%) - Diferença: {diferenca:+.2f}%")
        
        if diferenca > VARIACAO_BALANCEAMENTO:
            moedas_venda.append({
                'moeda': moeda,
                'diferenca': diferenca,
                'percentual_atual': pct_atual,
                'preco': precos.get(moeda, 0)
            })
            log_aviso(f"  → {moeda} acima do alvo (venda)")
        elif diferenca < -VARIACAO_BALANCEAMENTO:
            moedas_compra.append({
                'moeda': moeda,
                'diferenca': abs(diferenca),
                'percentual_atual': pct_atual,
                'preco': precos.get(moeda, 0)
            })
            log_aviso(f"  → {moeda} abaixo do alvo (compra)")
    
    # Executar vendas (ordenar por diferença decrescente)
    saldo_usdt_antes = saldos.get(MOEDA_BASE, 0)
    
    for item_venda in sorted(moedas_venda, key=lambda x: x['diferenca'], reverse=True):
        moeda = item_venda['moeda']
        preco = item_venda['preco']
        diferenca = item_venda['diferenca']
        quantidade_atual = saldos.get(moeda, 0)
        
        if quantidade_atual > 0 and preco > 0:
            # Calcular quantidade a vender para retornar ao percentual alvo
            valor_excesso = (diferenca / 100) * valor_portfolio
            quantidade_venda = valor_excesso / preco * (1 - COMISSAO)
            
            # Validar valor mínimo de operação na Binance
            eh_valido, msg_validacao = validar_valor_operacao(valor_excesso, moeda, 'venda')
            
            if eh_valido and quantidade_venda > 0 and quantidade_venda <= quantidade_atual:
                log_aviso(f"\n>>> Vendendo {moeda} <<<")
                log_info(f"    Preço Atual: {preco:.8f}")
                log_info(f"    Quantidade: {quantidade_venda:.8f}")
                log_info(f"    Valor: {valor_excesso:.2f} {MOEDA_BASE}")
                
                # Obter saldo ANTES da venda
                saldo_antes = obter_saldo(moeda)
                
                symbol = f"{moeda}/{MOEDA_BASE}"
                ordem = executar_ordem(symbol, 'sell', quantidade_venda)
                
                if ordem:
                    # Aguardar um pouco para a Binance processar
                    time.sleep(1)
                    
                    # Obter saldo DEPOIS da venda
                    saldo_depois = obter_saldo(moeda)
                    
                    # Se saldo não mudou, aguardar mais e tentar novamente
                    if saldo_depois == saldo_antes:
                        log_aviso(f"  Saldo não mudou na primeira verificação, aguardando mais 2s...")
                        time.sleep(2)
                        saldo_depois = obter_saldo(moeda)
                    
                    # Verificar se o saldo realmente diminuiu
                    diferenca_saldo = saldo_antes - saldo_depois
                    
                    if diferenca_saldo > 0:
                        log_sucesso(f"✓ Venda verificada: Saldo diminuiu {diferenca_saldo:.8f} {moeda}")
                        saldo_usdt_antes += valor_excesso
                        estado['historico_trades'].append({
                            'timestamp': datetime.now(timezone).isoformat(),
                            'moeda': moeda,
                            'tipo': 'venda_balanceamento',
                            'preco': preco,
                            'quantidade': quantidade_venda,
                            'valor': valor_excesso
                        })
                    else:
                        log_erro(f"✗ FALHA: Venda de {moeda} não confirmada! Saldo não diminuiu.")
                        log_erro(f"  Antes: {saldo_antes:.8f} {moeda} | Depois: {saldo_depois:.8f} {moeda}")
                    
                    time.sleep(1)
            elif not eh_valido:
                log_aviso(msg_validacao)
    
    # Atualizar saldos
    saldos = obter_saldos_portfolio(moedas)
    saldo_usdt_atual = saldos.get(MOEDA_BASE, 0)
    
    # PRÉ-CÁLCULO: Calcular total necessário para todas as compras
    compras_planejadas = []
    total_usdt_necessario = 0
    
    for item_compra in moedas_compra:
        moeda = item_compra['moeda']
        preco = item_compra['preco']
        diferenca = item_compra['diferenca']
        
        if preco > 0:
            valor_compra = (diferenca / 100) * valor_portfolio
            compras_planejadas.append({
                'moeda': moeda,
                'preco': preco,
                'diferenca': diferenca,
                'valor_ideal': valor_compra
            })
            total_usdt_necessario += valor_compra
    
    # Ordenar por diferença decrescente (prioridade)
    compras_planejadas = sorted(compras_planejadas, key=lambda x: x['diferenca'], reverse=True)
    
    # Se não há saldo suficiente, reduzir proporcionalmente todos os valores
    fator_reducao = 1.0
    if total_usdt_necessario > saldo_usdt_atual:
        fator_reducao = max(0, (saldo_usdt_atual - SALDO_MINIMO) / total_usdt_necessario) if total_usdt_necessario > 0 else 0
        if fator_reducao > 0:
            log_aviso(f"\n⚠ Saldo insuficiente para completar todas as compras. Reduzindo proporcionalmente a {fator_reducao*100:.1f}%")
    
    # Executar compras com distribuição justa do saldo
    for compra in compras_planejadas:
        moeda = compra['moeda']
        preco = compra['preco']
        valor_ideal = compra['valor_ideal']
        
        # Aplicar fator de redução se necessário
        valor_compra = valor_ideal * fator_reducao
        
        # Pular se valor for muito pequeno
        if valor_compra < VALOR_MINIMO_OPERACAO:
            log_aviso(f"  {moeda}: Valor ajustado ${valor_compra:.2f} abaixo do mínimo ${VALOR_MINIMO_OPERACAO:.2f}")
            continue
        
        if preco > 0 and saldo_usdt_atual >= VALOR_MINIMO_OPERACAO:
            quantidade_compra = valor_compra / preco * (1 - COMISSAO)
            
            # Validar valor mínimo de operação na Binance
            eh_valido, msg_validacao = validar_valor_operacao(valor_compra, moeda, 'compra')
            
            if eh_valido and valor_compra >= SALDO_MINIMO and saldo_usdt_atual >= valor_compra:
                log_aviso(f"\n>>> Comprando {moeda} <<<")
                log_info(f"    Preço Atual: {preco:.8f}")
                log_info(f"    Quantidade: {quantidade_compra:.8f}")
                log_info(f"    Valor: {valor_compra:.2f} {MOEDA_BASE} (Ideal: {valor_ideal:.2f})")
                
                # Verificar se o par está disponível ANTES de tentar comprar
                symbol = f"{moeda}/{MOEDA_BASE}"
                par_disponivel, info_par = verificar_par_disponivel(symbol)
                
                if not par_disponivel:
                    log_erro(f"  ⚠️ Par {symbol} não está disponível no testnet!")
                    log_erro(f"     {info_par}")
                    log_aviso(f"  Pulando compra de {moeda} - par indisponível")
                    continue
                
                log_info(f"  ✓ {info_par}")
                
                # Obter saldo ANTES da compra
                saldo_antes = obter_saldo(moeda)
                
                ordem = executar_ordem(symbol, 'buy', quantidade_compra)
                
                if ordem:
                    # Aguardar um pouco para a Binance processar
                    time.sleep(1)
                    
                    # Obter saldo DEPOIS da compra
                    saldo_depois = obter_saldo(moeda)
                    
                    # Se saldo não mudou, aguardar mais e tentar novamente
                    if saldo_depois == saldo_antes:
                        log_aviso(f"  Saldo não mudou na primeira verificação, aguardando mais 2s...")
                        time.sleep(2)
                        saldo_depois = obter_saldo(moeda)
                    
                    # Verificar se o saldo realmente aumentou
                    diferenca_saldo = saldo_depois - saldo_antes
                    
                    if diferenca_saldo > 0:
                        log_sucesso(f"✓ Compra verificada: Saldo aumentou {diferenca_saldo:.8f} {moeda}")
                        saldo_usdt_atual -= valor_compra
                        estado['historico_trades'].append({
                            'timestamp': datetime.now(timezone).isoformat(),
                            'moeda': moeda,
                            'tipo': 'compra_balanceamento',
                            'preco': preco,
                            'quantidade': quantidade_compra,
                            'valor': valor_compra
                        })
                    else:
                        log_erro(f"✗ FALHA: Compra de {moeda} não confirmada! Saldo não aumentou.")
                        log_erro(f"  Antes: {saldo_antes:.8f} {moeda} | Depois: {saldo_depois:.8f} {moeda}")
                    
                    time.sleep(1)
            elif not eh_valido:
                log_aviso(msg_validacao)
    
    log_info("\n--- Resumo do Balanceamento ---")
    
    # Atualizar saldos finais
    saldos = obter_saldos_portfolio(moedas)
    precos = {m: obter_preco_atual(f"{m}/{MOEDA_BASE}") for m in moedas}
    valor_portfolio_final = obter_valor_portfolio(moedas, saldos, precos)
    percentuais_finais = calcular_percentuais_atuais(moedas, saldos, precos, valor_portfolio_final)
    
    for moeda in moedas:
        log_info(f"{moeda}: {percentuais_finais.get(moeda, 0):.2f}% (Saldo: {saldos.get(moeda, 0):.8f})")
    
    log_info(f"Valor Total Final: {valor_portfolio_final:.2f} {MOEDA_BASE}")
    
    salvar_estado_portfolio(PORTFOLIO_STATE_FILE, estado)
    return estado

def relatorio_portfolio():
    """Exibe relatório detalhado do portfólio"""
    try:
        estado = carregar_estado_portfolio(PORTFOLIO_STATE_FILE)
        if not estado or not isinstance(estado, dict):
            log_aviso("Portfólio não inicializado ou corrompido")
            return None
        
        moedas = [m['moeda'] for m in PORTFOLIO_CONFIG if m.get('ativa', True)]
        saldos = obter_saldos_portfolio(moedas)
        precos = {m: obter_preco_atual(f"{m}/{MOEDA_BASE}") for m in moedas}
        valor_portfolio = obter_valor_portfolio(moedas, saldos, precos)
        percentuais = calcular_percentuais_atuais(moedas, saldos, precos, valor_portfolio)
        
        # ✓ NOVO: Atualizar melhores preços dos últimos 3 dias
        estado = atualizar_melhores_precos_portfolio(estado, moedas)
        
        log_info("\n" + "=" * 70)
        log_info("RELATÓRIO DO PORTFÓLIO")
        log_info("=" * 70)
        log_info(f"Fase: {estado.get('fase', 'desconhecida').upper()}")
        log_info(f"Data de Início: {estado.get('data_inicio', 'N/A')}")
        log_info(f"Capital Inicial: {estado.get('capital_inicial', 0)} {MOEDA_BASE}")
        log_info(f"Valor Atual: {valor_portfolio:.2f} {MOEDA_BASE}")
        
        capital = estado.get('capital_inicial', 0)
        if capital > 0:
            log_info(f"Ganho/Perda: {valor_portfolio - capital:+.2f} {MOEDA_BASE}")
            log_info(f"Ganho %: {((valor_portfolio / capital) - 1) * 100:+.2f}%")
        
        log_info("-" * 70)
        
        for moeda in moedas:
            saldo = saldos.get(moeda, 0)
            preco = precos.get(moeda, 0)
            valor = saldo * preco if preco else 0
            pct = percentuais.get(moeda, 0)
            pct_alvo = estado.get('percentuais_alvo', {}).get(moeda, 0)
            
            log_info(f"{moeda}: Saldo={saldo:15.8f} | " +
                f"Valor: {valor:12.2f} | {pct:6.2f}% (Alvo: {pct_alvo:.2f}%)")
        
        log_info("=" * 70 + "\n")
        
        # ✓ Retornar estado com best_prices_3days atualizado
        return estado
    except Exception as e:
        log_erro(f"Erro ao exibir relatório: {e}")
        traceback.print_exc()
        return None

# ===================== MAIN LOOP =====================

def main():
    """Função principal"""
    log_info("\n" + "=" * 70)
    log_info("ROBÔ DE BALANCEAMENTO DE PORTFÓLIO - INICIADO")
    log_info("=" * 70)
    
    estado, moedas = inicializar_portfolio()
    contador_ciclo = 0
    contador_relatorio = 0
    
    try:
        while True:
            contador_ciclo += 1
            contador_relatorio += 1
            
            try:
                if estado['fase'] == 'construcao':
                    estado = fase_construcao_portfolio(estado, moedas)
                elif estado['fase'] == 'balanceamento':
                    estado = fase_balanceamento_portfolio(estado, moedas)
                
                if contador_relatorio >= TEMPO_REFRESH:
                    estado_atualizado = relatorio_portfolio()
                    if estado_atualizado:
                        estado = estado_atualizado  # Atualizar estado com best_prices_3days
                    contador_relatorio = 0
                
                log_info(f"\nPróximo ciclo em {TEMPO_CICLO} segundos...")
                time.sleep(TEMPO_CICLO)
                
            except Exception as e:
                log_erro(f"Erro no ciclo {contador_ciclo}: {e}")
                traceback.print_exc()
                time.sleep(TEMPO_CICLO)
    
    except KeyboardInterrupt:
        log_info("\nRobô interrompido pelo usuário")
        relatorio_portfolio()
    except Exception as e:
        log_erro(f"Erro fatal: {e}")
        traceback.print_exc()

if __name__ == "__main__":
    main()
