3 market api #8

Merged
Simo93-rgb merged 25 commits from 3-market-api into main 2025-10-01 15:51:25 +02:00
8 changed files with 47 additions and 250 deletions
Showing only changes of commit 66d2838df3 - Show all commits

View File

@@ -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=

View File

@@ -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

View File

@@ -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):

View File

@@ -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)

View File

@@ -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)

View File

@@ -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]}"

View File

@@ -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")

View File

@@ -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__])