# -*- 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))
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%)")

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 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 executar_ordem(symbol, side, quantidade):
    """Executa uma ordem de compra ou venda"""
    try:
        ordem = exchange.create_market_order(symbol, side, quantidade)
        timestamp = datetime.now(timezone).strftime("%d/%m/%Y %H:%M:%S")
        
        if side == 'sell':
            log_sucesso(f"Ordem de VENDA executada: {quantidade} {symbol} em {timestamp}")
        else:
            log_sucesso(f"Ordem de COMPRA executada: {quantidade} {symbol} em {timestamp}")
        
        return ordem
    except Exception as e:
        log_erro(f"Falha ao executar ordem {side} para {symbol} ({quantidade}): {e}")
        return None

def aplicar_comissao(quantidade):
    """Aplica comissão à quantidade"""
    return quantidade * (1 - COMISSAO)

# ===================== 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
                
                if 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}")
                    
                    symbol = f"{moeda}/{MOEDA_BASE}"
                    ordem = executar_ordem(symbol, 'buy', quantidade)
                    
                    if ordem:
                        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
                        })
                        time.sleep(2)  # Pausa entre trades
                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)
            
            if 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}")
                
                symbol = f"{moeda}/{MOEDA_BASE}"
                ordem = executar_ordem(symbol, 'sell', quantidade_venda)
                
                if ordem:
                    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
                    })
                    time.sleep(2)
    
    # Atualizar saldos
    saldos = obter_saldos_portfolio(moedas)
    saldo_usdt_atual = saldos.get(MOEDA_BASE, 0)
    
    # Executar compras (ordenar por quantidade de falta decrescente)
    for item_compra in sorted(moedas_compra, key=lambda x: x['diferenca'], reverse=True):
        moeda = item_compra['moeda']
        preco = item_compra['preco']
        diferenca = item_compra['diferenca']
        
        if preco > 0 and saldo_usdt_atual >= SALDO_MINIMO:
            # Calcular quantidade a comprar
            valor_compra = (diferenca / 100) * valor_portfolio
            quantidade_compra = valor_compra / preco * (1 - COMISSAO)
            
            if 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}")
                
                symbol = f"{moeda}/{MOEDA_BASE}"
                ordem = executar_ordem(symbol, 'buy', quantidade_compra)
                
                if ordem:
                    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
                    })
                    time.sleep(2)
    
    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
        
        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)
        
        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")
    except Exception as e:
        log_erro(f"Erro ao exibir relatório: {e}")
        traceback.print_exc()
        pct = percentuais.get(moeda, 0)
        pct_alvo = estado['percentuais_alvo'].get(moeda, 0)
        
        log_info(f"{moeda:8s} | Saldo: {saldo:15.8f} | Preço: {preco:15.8f} | "
                f"Valor: {valor:12.2f} | {pct:6.2f}% (Alvo: {pct_alvo:.2f}%)")
    
    log_info("=" * 70 + "\n")

# ===================== 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:
                    relatorio_portfolio()
                    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()
