| | import os |
| | import ccxt.async_support as ccxt |
| | from typing import Dict, Any, List, Optional, Tuple |
| |
|
| |
|
| | |
| | |
| | |
| |
|
| | |
| | CCXT_EXCHANGE_ID_ENV = os.environ.get("CCXT_EXCHANGE_ID", "binance") |
| | CCXT_API_KEY_ENV = os.environ.get("CCXT_API_KEY") |
| | CCXT_API_SECRET_ENV = os.environ.get("CCXT_API_SECRET") |
| | CCXT_API_PASSWORD_ENV = os.environ.get("CCXT_API_PASSWORD") |
| | CCXT_SANDBOX_MODE_ENV = os.environ.get("CCXT_SANDBOX_MODE", "false").lower() == "true" |
| |
|
| |
|
| | async def get_ccxt_exchange(logger_instance) -> Optional[ccxt.Exchange]: |
| | """ |
| | Inicializa e retorna uma instância da exchange ccxt. |
| | Retorna None se a configuração estiver ausente ou a inicialização falhar. |
| | """ |
| | if not CCXT_API_KEY_ENV or not CCXT_API_SECRET_ENV: |
| | logger_instance.warning("CCXT_API_KEY ou CCXT_API_SECRET não configurados. Não é possível inicializar a exchange.") |
| | return None |
| |
|
| | try: |
| | exchange_class = getattr(ccxt, CCXT_EXCHANGE_ID_ENV) |
| | config = { |
| | 'apiKey': CCXT_API_KEY_ENV, |
| | 'secret': CCXT_API_SECRET_ENV, |
| | 'enableRateLimit': True, |
| | } |
| | if CCXT_API_PASSWORD_ENV: |
| | config['password'] = CCXT_API_PASSWORD_ENV |
| | |
| | exchange = exchange_class(config) |
| |
|
| | if CCXT_SANDBOX_MODE_ENV: |
| | |
| | |
| | |
| | if hasattr(exchange, 'set_sandbox_mode') and callable(exchange.set_sandbox_mode): |
| | try: |
| | exchange.set_sandbox_mode(True) |
| | logger_instance.info(f"CCXT: Modo SANDBOX ativado para {CCXT_EXCHANGE_ID_ENV} via set_sandbox_mode.") |
| | except Exception as e_sandbox: |
| | logger_instance.warning(f"CCXT: Tentativa de set_sandbox_mode para {CCXT_EXCHANGE_ID_ENV} falhou: {e_sandbox}. Tentando URL de teste.") |
| | |
| | if 'test' in exchange.urls: |
| | exchange.urls['api'] = exchange.urls['test'] |
| | logger_instance.info(f"CCXT: URLs alteradas para TESTNET para {CCXT_EXCHANGE_ID_ENV} (fallback).") |
| | else: |
| | logger_instance.warning(f"CCXT: Nenhuma URL de teste encontrada para {CCXT_EXCHANGE_ID_ENV} como fallback.") |
| | |
| | elif 'test' in exchange.urls: |
| | exchange.urls['api'] = exchange.urls['test'] |
| | logger_instance.info(f"CCXT: URLs alteradas para TESTNET para {CCXT_EXCHANGE_ID_ENV} (método direto).") |
| | else: |
| | logger_instance.warning(f"CCXT: Modo SANDBOX solicitado, mas não há método set_sandbox_mode nem URL de teste para {CCXT_EXCHANGE_ID_ENV}.") |
| | |
| | |
| | |
| | |
| | return exchange |
| |
|
| | except AttributeError: |
| | logger_instance.error(f"CCXT: Exchange ID '{CCXT_EXCHANGE_ID_ENV}' inválida ou não suportada.") |
| | return None |
| | except Exception as e: |
| | logger_instance.error(f"CCXT: Erro ao inicializar exchange {CCXT_EXCHANGE_ID_ENV}: {str(e)}", exc_info=True) |
| | return None |
| |
|
| |
|
| | async def fetch_crypto_data( |
| | exchange: ccxt.Exchange, |
| | pairs: List[str], |
| | logger_instance |
| | ) -> Tuple[Dict[str, Any], bool, str]: |
| | """ |
| | Busca dados de mercado (ticker, OHLCV) para uma lista de pares de cripto. |
| | Retorna: (dados_coletados, sucesso, mensagem_de_erro_detalhada) |
| | """ |
| | collected_data: Dict[str, Any] = {} |
| | fetch_successful = True |
| | error_message = "" |
| |
|
| | logger_instance.info(f"CCXT: Iniciando coleta de dados para pares: {pairs}") |
| |
|
| | for pair_symbol in pairs: |
| | pair_data_key = pair_symbol.replace("/", "_") |
| | current_pair_data = {} |
| | try: |
| | if exchange.has['fetchTicker']: |
| | ticker = await exchange.fetch_ticker(pair_symbol) |
| | current_pair_data['ticker'] = { |
| | 'last': ticker.get('last'), 'bid': ticker.get('bid'), 'ask': ticker.get('ask'), |
| | 'volume': ticker.get('baseVolume'), 'timestamp': ticker.get('timestamp') |
| | } |
| | |
| | if exchange.has['fetchOHLCV']: |
| | |
| | ohlcv = await exchange.fetch_ohlcv(pair_symbol, timeframe='1h', limit=72) |
| | current_pair_data['ohlcv_1h'] = ohlcv |
| | |
| | |
| | |
| | collected_data[pair_data_key] = current_pair_data |
| | logger_instance.info(f"CCXT: Dados coletados para {pair_symbol}: Ticker OK, OHLCV OK (len: {len(ohlcv if 'ohlcv' in current_pair_data else [])})") |
| |
|
| | except ccxt.NetworkError as e_net: |
| | logger_instance.error(f"CCXT: Erro de REDE ao buscar dados para {pair_symbol}: {e_net}") |
| | error_message += f"NetworkError for {pair_symbol}: {e_net}; " |
| | fetch_successful = False |
| | break |
| | except ccxt.ExchangeError as e_exc: |
| | logger_instance.error(f"CCXT: Erro da EXCHANGE ao buscar dados para {pair_symbol}: {e_exc}") |
| | error_message += f"ExchangeError for {pair_symbol}: {e_exc}; " |
| | fetch_successful = False |
| | |
| | |
| | |
| | collected_data[pair_data_key] = {"error": str(e_exc)} |
| | except Exception as e_gen: |
| | logger_instance.error(f"CCXT: Erro GERAL ao buscar dados para {pair_symbol}: {e_gen}", exc_info=True) |
| | error_message += f"General error for {pair_symbol}: {e_gen}; " |
| | fetch_successful = False |
| | collected_data[pair_data_key] = {"error": str(e_gen)} |
| |
|
| | if not fetch_successful: |
| | logger_instance.warning(f"CCXT: Coleta de dados de cripto encontrou erros. Detalhes: {error_message}") |
| | else: |
| | logger_instance.info("CCXT: Coleta de dados de cripto concluída com sucesso.") |
| | |
| | return collected_data, fetch_successful, error_message |
| |
|
| | |
| | |
| | |
| |
|
| | |