diff --git a/.env.example b/.env.example index 85a072d..4cfc34a 100644 --- a/.env.example +++ b/.env.example @@ -6,9 +6,6 @@ # Vedi https://docs.agno.com/examples/models per vedere tutti i modelli supportati GOOGLE_API_KEY= -# Inserire il percorso di installazione di ollama (es. /usr/share/ollama/.ollama) -# attenzione che fra Linux nativo e WSL il percorso è diverso -OLLAMA_MODELS_PATH= ############################################################################### # Configurazioni per gli agenti di mercato ############################################################################### @@ -20,9 +17,8 @@ COINBASE_API_SECRET= # https://www.cryptocompare.com/cryptopian/api-keys CRYPTOCOMPARE_API_KEY= -# Binance API per Market Agent -# Ottenibili da: https://www.binance.com/en/my/settings/api-management -# Supporta sia API autenticate che pubbliche (PublicBinance) +# https://www.binance.com/en/my/settings/api-management +# Non necessario per operazioni in sola lettura BINANCE_API_KEY= BINANCE_API_SECRET= diff --git a/demos/market_providers_api_demo.py b/demos/market_providers_api_demo.py index 2c3a8f3..fea2245 100644 --- a/demos/market_providers_api_demo.py +++ b/demos/market_providers_api_demo.py @@ -30,7 +30,6 @@ from app.markets import ( CoinBaseWrapper, CryptoCompareWrapper, BinanceWrapper, - PublicBinanceAgent, BaseWrapper ) @@ -239,13 +238,6 @@ def initialize_providers() -> Dict[str, BaseWrapper]: providers = {} env_vars = check_environment_variables() - # PublicBinanceAgent (sempre disponibile) - try: - providers["PublicBinance"] = PublicBinanceAgent() - print("✅ PublicBinanceAgent inizializzato con successo") - except Exception as e: - print(f"❌ Errore nell'inizializzazione di PublicBinanceAgent: {e}") - # CryptoCompareWrapper if env_vars["CRYPTOCOMPARE_API_KEY"]: try: @@ -267,14 +259,12 @@ def initialize_providers() -> Dict[str, BaseWrapper]: print("⚠️ CoinBaseWrapper saltato: credenziali Coinbase non complete") # BinanceWrapper - if env_vars["BINANCE_API_KEY"] and env_vars["BINANCE_API_SECRET"]: - try: - providers["Binance"] = BinanceWrapper() - print("✅ BinanceWrapper inizializzato con successo") - except Exception as e: - print(f"❌ Errore nell'inizializzazione di BinanceWrapper: {e}") - else: - print("⚠️ BinanceWrapper saltato: credenziali Binance non complete") + + try: + providers["Binance"] = BinanceWrapper() + print("✅ BinanceWrapper inizializzato con successo") + except Exception as e: + print(f"❌ Errore nell'inizializzazione di BinanceWrapper: {e}") return providers diff --git a/src/app/markets/base.py b/src/app/markets/base.py index 2690c4f..117c174 100644 --- a/src/app/markets/base.py +++ b/src/app/markets/base.py @@ -3,16 +3,47 @@ from pydantic import BaseModel class BaseWrapper: """ - Interfaccia per i wrapper delle API di mercato. - Implementa i metodi di base che ogni wrapper deve avere. + Base class for market API wrappers. + All market API wrappers should inherit from this class and implement the methods. """ + def get_product(self, asset_id: str) -> 'ProductInfo': + """ + Get product information for a specific asset ID. + Args: + asset_id (str): The asset ID to retrieve information for. + Returns: + ProductInfo: An object containing product information. + """ raise NotImplementedError + def get_products(self, asset_ids: list[str]) -> list['ProductInfo']: + """ + Get product information for multiple asset IDs. + Args: + asset_ids (list[str]): The list of asset IDs to retrieve information for. + Returns: + list[ProductInfo]: A list of objects containing product information. + """ raise NotImplementedError + def get_all_products(self) -> list['ProductInfo']: + """ + Get product information for all available assets. + Returns: + list[ProductInfo]: A list of objects containing product information. + """ raise NotImplementedError + def get_historical_prices(self, asset_id: str = "BTC", limit: int = 100) -> list['Price']: + """ + Get historical price data for a specific asset ID. + Args: + asset_id (str): The asset ID to retrieve price data for. + limit (int): The maximum number of price data points to return. + Returns: + list[Price]: A list of Price objects. + """ raise NotImplementedError class ProductInfo(BaseModel): diff --git a/src/app/markets/binance.py b/src/app/markets/binance.py index 6b6b6d3..d5dfe10 100644 --- a/src/app/markets/binance.py +++ b/src/app/markets/binance.py @@ -23,10 +23,7 @@ class BinanceWrapper(BaseWrapper): def __init__(self, currency: str = "USDT"): api_key = os.getenv("BINANCE_API_KEY") - assert api_key is not None, "API key is required" - api_secret = os.getenv("BINANCE_API_SECRET") - assert api_secret is not None, "API secret is required" self.currency = currency self.client = Client(api_key=api_key, api_secret=api_secret) diff --git a/src/app/markets/binance_public.py b/src/app/markets/binance_public.py deleted file mode 100644 index c1d9896..0000000 --- a/src/app/markets/binance_public.py +++ /dev/null @@ -1,218 +0,0 @@ -""" -Versione pubblica di Binance per accesso ai dati pubblici senza autenticazione. - -Questa implementazione estende BaseWrapper per mantenere coerenza -con l'architettura del modulo markets. -""" - -from typing import Optional, Dict, Any -from datetime import datetime, timedelta -from binance.client import Client -from .base import BaseWrapper, ProductInfo, Price - - -class PublicBinanceAgent(BaseWrapper): - """ - Agent per l'accesso ai dati pubblici di Binance. - - Utilizza l'API pubblica di Binance per ottenere informazioni - sui prezzi e sui mercati senza richiedere autenticazione. - """ - - def __init__(self): - """Inizializza il client pubblico senza credenziali.""" - self.client = Client() - - def __format_symbol(self, asset_id: str) -> str: - """ - Formatta l'asset_id per Binance (es. BTC -> BTCUSDT). - - Args: - asset_id: ID dell'asset (es. "BTC", "ETH") - - Returns: - Simbolo formattato per Binance - """ - if asset_id.endswith("USDT") or asset_id.endswith("BUSD"): - return asset_id - return f"{asset_id}USDT" - - def get_product(self, asset_id: str) -> ProductInfo: - """ - Ottiene informazioni su un singolo prodotto. - - Args: - asset_id: ID dell'asset (es. "BTC") - - Returns: - Oggetto ProductInfo con le informazioni del prodotto - """ - symbol = self.__format_symbol(asset_id) - try: - ticker = self.client.get_symbol_ticker(symbol=symbol) - ticker_24h = self.client.get_ticker(symbol=symbol) - return ProductInfo.from_binance(ticker, ticker_24h) - except Exception as e: - print(f"Errore nel recupero del prodotto {asset_id}: {e}") - return ProductInfo(id=asset_id, symbol=asset_id) - - def get_products(self, asset_ids: list[str]) -> list[ProductInfo]: - """ - Ottiene informazioni su più prodotti. - - Args: - asset_ids: Lista di ID degli asset - - Returns: - Lista di oggetti ProductInfo - """ - products = [] - for asset_id in asset_ids: - product = self.get_product(asset_id) - products.append(product) - return products - - def get_all_products(self) -> list[ProductInfo]: - """ - Ottiene informazioni su tutti i prodotti disponibili. - - Returns: - Lista di oggetti ProductInfo per i principali asset - """ - # Per la versione pubblica, restituiamo solo i principali asset - major_assets = ["BTC", "ETH", "BNB", "ADA", "DOT", "LINK", "LTC", "XRP"] - return self.get_products(major_assets) - - def get_historical_prices(self, asset_id: str = "BTC") -> list[Price]: - """ - Ottiene i prezzi storici per un asset. - - Args: - asset_id: ID dell'asset (default: "BTC") - - Returns: - Lista di oggetti Price con i dati storici - """ - symbol = self.__format_symbol(asset_id) - try: - # Ottieni candele degli ultimi 30 giorni - end_time = datetime.now() - start_time = end_time - timedelta(days=30) - - klines = self.client.get_historical_klines( - symbol, - Client.KLINE_INTERVAL_1DAY, - start_time.strftime("%d %b %Y %H:%M:%S"), - end_time.strftime("%d %b %Y %H:%M:%S") - ) - - prices = [] - for kline in klines: - price = Price( - open=float(kline[1]), - high=float(kline[2]), - low=float(kline[3]), - close=float(kline[4]), - volume=float(kline[5]), - time=str(datetime.fromtimestamp(kline[0] / 1000)) - ) - prices.append(price) - - return prices - except Exception as e: - print(f"Errore nel recupero dei prezzi storici per {asset_id}: {e}") - return [] - - def get_public_prices(self, symbols: Optional[list[str]] = None) -> Optional[Dict[str, Any]]: - """ - Ottiene i prezzi pubblici per i simboli specificati. - - Args: - symbols: Lista di simboli da recuperare (es. ["BTCUSDT", "ETHUSDT"]). - Se None, recupera BTC e ETH di default. - - Returns: - Dizionario con i prezzi e informazioni sulla fonte, o None in caso di errore. - """ - if symbols is None: - symbols = ["BTCUSDT", "ETHUSDT"] - - try: - prices = {} - for symbol in symbols: - ticker = self.client.get_symbol_ticker(symbol=symbol) - # Converte BTCUSDT -> BTC_USD per consistenza - clean_symbol = symbol.replace("USDT", "_USD").replace("BUSD", "_USD") - prices[clean_symbol] = float(ticker['price']) - - return { - **prices, - 'source': 'binance_public', - 'timestamp': self.client.get_server_time()['serverTime'] - } - except Exception as e: - print(f"Errore nel recupero dei prezzi pubblici: {e}") - return None - - def get_24hr_ticker(self, symbol: str) -> Optional[Dict[str, Any]]: - """ - Ottiene le statistiche 24h per un simbolo specifico. - - Args: - symbol: Simbolo del trading pair (es. "BTCUSDT") - - Returns: - Dizionario con le statistiche 24h o None in caso di errore. - """ - try: - ticker = self.client.get_ticker(symbol=symbol) - return { - 'symbol': ticker['symbol'], - 'price': float(ticker['lastPrice']), - 'price_change': float(ticker['priceChange']), - 'price_change_percent': float(ticker['priceChangePercent']), - 'high_24h': float(ticker['highPrice']), - 'low_24h': float(ticker['lowPrice']), - 'volume_24h': float(ticker['volume']), - 'source': 'binance_public' - } - except Exception as e: - print(f"Errore nel recupero del ticker 24h per {symbol}: {e}") - return None - - def get_exchange_info(self) -> Optional[Dict[str, Any]]: - """ - Ottiene informazioni generali sull'exchange. - - Returns: - Dizionario con informazioni sull'exchange o None in caso di errore. - """ - try: - info = self.client.get_exchange_info() - return { - 'timezone': info['timezone'], - 'server_time': info['serverTime'], - 'symbols_count': len(info['symbols']), - 'source': 'binance_public' - } - except Exception as e: - print(f"Errore nel recupero delle informazioni exchange: {e}") - return None - - -# Esempio di utilizzo -if __name__ == "__main__": - # Uso senza credenziali - public_agent = PublicBinanceAgent() - - # Ottieni prezzi di default (BTC e ETH) - public_prices = public_agent.get_public_prices() - print("Prezzi pubblici:", public_prices) - - # Ottieni statistiche 24h per BTC - btc_stats = public_agent.get_24hr_ticker("BTCUSDT") - print("Statistiche BTC 24h:", btc_stats) - - # Ottieni informazioni exchange - exchange_info = public_agent.get_exchange_info() - print("Info exchange:", exchange_info) \ No newline at end of file diff --git a/tests/conftest.py b/tests/conftest.py index e65e86f..c792e04 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -23,6 +23,7 @@ def pytest_configure(config:pytest.Config): ("social", "marks tests that use social media"), ("limited", "marks tests that have limited execution due to API constraints"), ("wrapper", "marks tests for wrapper handler"), + ("tools", "marks tests for tools"), ] for marker in markers: line = f"{marker[0]}: {marker[1]}" diff --git a/tests/agents/test_market.py b/tests/tools/test_market_tool.py similarity index 98% rename from tests/agents/test_market.py rename to tests/tools/test_market_tool.py index 10e5cb4..0d6d1a1 100644 --- a/tests/agents/test_market.py +++ b/tests/tools/test_market_tool.py @@ -4,6 +4,8 @@ from app.agents.market_agent import MarketToolkit from app.markets import MarketAPIsTool @pytest.mark.limited # usa molte api calls e non voglio esaurire le chiavi api +@pytest.mark.tools +@pytest.mark.api class TestMarketAPIsTool: def test_wrapper_initialization(self): market_wrapper = MarketAPIsTool("USD") diff --git a/tests/test_market_data_aggregator.py b/tests/utils/test_market_data_aggregator.py similarity index 98% rename from tests/test_market_data_aggregator.py rename to tests/utils/test_market_data_aggregator.py index 604362c..236d2a4 100644 --- a/tests/test_market_data_aggregator.py +++ b/tests/utils/test_market_data_aggregator.py @@ -1,10 +1,12 @@ import pytest - from app.utils.market_data_aggregator import MarketDataAggregator from app.utils.aggregated_models import AggregatedProductInfo from app.markets.base import ProductInfo, Price +@pytest.mark.limited +@pytest.mark.market +@pytest.mark.api class TestMarketDataAggregator: def test_initialization(self): @@ -84,7 +86,3 @@ class TestMarketDataAggregator: assert len(aggregated._metadata.sources_used) > 0 assert aggregated._metadata.aggregation_timestamp != "" # La confidence può essere 0.0 se ci sono fonti "unknown" - - -if __name__ == "__main__": - pytest.main([__file__]) \ No newline at end of file