diff --git a/src/app/markets/base.py b/src/app/markets/base.py index 032f8aa..b15b125 100644 --- a/src/app/markets/base.py +++ b/src/app/markets/base.py @@ -12,7 +12,7 @@ class BaseWrapper: raise NotImplementedError def get_all_products(self) -> list['ProductInfo']: raise NotImplementedError - def get_historical_prices(self, asset_id: str = "BTC") -> list['Price']: + def get_historical_prices(self, asset_id: str = "BTC", limit: int = 100) -> list['Price']: raise NotImplementedError class ProductInfo(BaseModel): diff --git a/src/app/markets/coinbase.py b/src/app/markets/coinbase.py index aac556d..38614aa 100644 --- a/src/app/markets/coinbase.py +++ b/src/app/markets/coinbase.py @@ -1,19 +1,32 @@ import os +from enum import Enum +from datetime import datetime, timedelta from coinbase.rest import RESTClient from app.markets.base import ProductInfo, BaseWrapper, Price + +class Granularity(Enum): + UNKNOWN_GRANULARITY = 0 + ONE_MINUTE = 60 + FIVE_MINUTE = 300 + FIFTEEN_MINUTE = 900 + THIRTY_MINUTE = 1800 + ONE_HOUR = 3600 + TWO_HOUR = 7200 + FOUR_HOUR = 14400 + SIX_HOUR = 21600 + ONE_DAY = 86400 + class CoinBaseWrapper(BaseWrapper): """ Wrapper per le API di Coinbase. La documentazione delle API è disponibile qui: https://docs.cdp.coinbase.com/api-reference/advanced-trade-api/rest-api/introduction """ - def __init__(self, api_key:str = None, api_private_key:str = None, currency: str = "USD"): - if api_key is None: - api_key = os.getenv("COINBASE_API_KEY") + def __init__(self, currency: str = "USD"): + api_key = os.getenv("COINBASE_API_KEY") assert api_key is not None, "API key is required" - if api_private_key is None: - api_private_key = os.getenv("COINBASE_API_SECRET") + api_private_key = os.getenv("COINBASE_API_SECRET") assert api_private_key is not None, "API private key is required" self.currency = currency @@ -32,14 +45,23 @@ class CoinBaseWrapper(BaseWrapper): def get_products(self, asset_ids: list[str]) -> list[ProductInfo]: all_asset_ids = [self.__format(asset_id) for asset_id in asset_ids] - assets = self.client.get_products(all_asset_ids) + assets = self.client.get_products(product_ids=all_asset_ids) return [ProductInfo.from_coinbase(asset) for asset in assets.products] def get_all_products(self) -> list[ProductInfo]: assets = self.client.get_products() return [ProductInfo.from_coinbase(asset) for asset in assets.products] - def get_historical_prices(self, asset_id: str = "BTC") -> list[Price]: + def get_historical_prices(self, asset_id: str = "BTC", limit: int = 100) -> list[Price]: asset_id = self.__format(asset_id) - data = self.client.get_candles(product_id=asset_id) + end_time = datetime.now() + start_time = end_time - timedelta(days=14) + + data = self.client.get_candles( + product_id=asset_id, + granularity=Granularity.ONE_HOUR.name, + start=str(int(start_time.timestamp())), + end=str(int(end_time.timestamp())), + limit=limit + ) return [Price.from_coinbase(candle) for candle in data.candles] diff --git a/src/app/markets/cryptocompare.py b/src/app/markets/cryptocompare.py index 188a2c2..b4a092d 100644 --- a/src/app/markets/cryptocompare.py +++ b/src/app/markets/cryptocompare.py @@ -10,9 +10,8 @@ class CryptoCompareWrapper(BaseWrapper): La documentazione delle API è disponibile qui: https://developers.coindesk.com/documentation/legacy/Price/SingleSymbolPriceEndpoint !!ATTENZIONE!! sembra essere una API legacy e potrebbe essere deprecata in futuro. """ - def __init__(self, api_key:str = None, currency:str='USD'): - if api_key is None: - api_key = os.getenv("CRYPTOCOMPARE_API_KEY") + def __init__(self, currency:str='USD'): + api_key = os.getenv("CRYPTOCOMPARE_API_KEY") assert api_key is not None, "API key is required" self.api_key = api_key @@ -49,12 +48,11 @@ class CryptoCompareWrapper(BaseWrapper): def get_all_products(self) -> list[ProductInfo]: raise NotImplementedError("CryptoCompare does not support fetching all assets") - def get_historical_prices(self, asset_id: str, day_back: int = 10) -> list[dict]: - assert day_back <= 30, "day_back should be less than or equal to 30" + def get_historical_prices(self, asset_id: str, limit: int = 100) -> list[dict]: response = self.__request("/data/v2/histohour", params = { "fsym": asset_id, "tsym": self.currency, - "limit": day_back * 24 + "limit": limit-1 # because the API returns limit+1 items (limit + current) }) data = response.get('Data', {}).get('Data', []) diff --git a/tests/agents/test_market.py b/tests/agents/test_market.py index 56931b3..b3e7f46 100644 --- a/tests/agents/test_market.py +++ b/tests/agents/test_market.py @@ -1,34 +1,21 @@ import os import pytest from app.agents.market import MarketToolkit -from app.markets.base import BaseWrapper -from app.markets.coinbase import CoinBaseWrapper -from app.markets.cryptocompare import CryptoCompareWrapper from app.markets import MarketAPIs class TestMarketSystem: """Test suite per il sistema di mercato (wrappers + toolkit)""" - @pytest.fixture(scope="class") - def market_wrapper(self) -> BaseWrapper: - return MarketAPIs("USD") - - def test_wrapper_initialization(self, market_wrapper): + def test_wrapper_initialization(self): + market_wrapper = MarketAPIs("USD") assert market_wrapper is not None assert hasattr(market_wrapper, 'get_product') assert hasattr(market_wrapper, 'get_products') assert hasattr(market_wrapper, 'get_all_products') assert hasattr(market_wrapper, 'get_historical_prices') - def test_providers_configuration(self): - available_providers = [] - if os.getenv('CDP_API_KEY_NAME') and os.getenv('CDP_API_PRIVATE_KEY'): - available_providers.append('coinbase') - if os.getenv('CRYPTOCOMPARE_API_KEY'): - available_providers.append('cryptocompare') - assert len(available_providers) > 0 - - def test_wrapper_capabilities(self, market_wrapper): + def test_wrapper_capabilities(self): + market_wrapper = MarketAPIs("USD") capabilities = [] if hasattr(market_wrapper, 'get_product'): capabilities.append('single_product') @@ -38,14 +25,15 @@ class TestMarketSystem: capabilities.append('historical_data') assert len(capabilities) > 0 - def test_market_data_retrieval(self, market_wrapper): + def test_market_data_retrieval(self): + market_wrapper = MarketAPIs("USD") btc_product = market_wrapper.get_product("BTC") assert btc_product is not None assert hasattr(btc_product, 'symbol') assert hasattr(btc_product, 'price') assert btc_product.price > 0 - def test_market_toolkit_integration(self, market_wrapper): + def test_market_toolkit_integration(self): try: toolkit = MarketToolkit() assert toolkit is not None @@ -59,59 +47,6 @@ class TestMarketSystem: print(f"MarketToolkit test failed: {e}") # Non fail completamente - il toolkit potrebbe avere dipendenze specifiche - @pytest.mark.skipif( - not os.getenv('CRYPTOCOMPARE_API_KEY'), - reason="CRYPTOCOMPARE_API_KEY not configured" - ) - def test_cryptocompare_wrapper(self): - try: - api_key = os.getenv('CRYPTOCOMPARE_API_KEY') - wrapper = CryptoCompareWrapper(api_key=api_key, currency="USD") - - btc_product = wrapper.get_product("BTC") - assert btc_product is not None - assert btc_product.symbol == "BTC" - assert btc_product.price > 0 - - products = wrapper.get_products(["BTC", "ETH"]) - assert isinstance(products, list) - assert len(products) > 0 - - for product in products: - if product.symbol in ["BTC", "ETH"]: - assert product.price > 0 - - except Exception as e: - print(f"CryptoCompare test failed: {e}") - # Non fail il test se c'è un errore di rete o API - - @pytest.mark.skipif( - not (os.getenv('CDP_API_KEY_NAME') and os.getenv('CDP_API_PRIVATE_KEY')), - reason="Coinbase credentials not configured" - ) - def test_coinbase_wrapper(self): - try: - api_key = os.getenv('CDP_API_KEY_NAME') - api_secret = os.getenv('CDP_API_PRIVATE_KEY') - wrapper = CoinBaseWrapper( - api_key=api_key, - api_private_key=api_secret, - currency="USD" - ) - - btc_product = wrapper.get_product("BTC") - assert btc_product is not None - assert btc_product.symbol == "BTC" - assert btc_product.price > 0 - - products = wrapper.get_products(["BTC", "ETH"]) - assert isinstance(products, list) - assert len(products) > 0 - - except Exception as e: - print(f"Coinbase test failed: {e}") - # Non fail il test se c'è un errore di credenziali o rete - def test_provider_selection_mechanism(self): potential_providers = 0 if os.getenv('CDP_API_KEY_NAME') and os.getenv('CDP_API_PRIVATE_KEY'): @@ -127,8 +62,9 @@ class TestMarketSystem: assert wrapper is not None assert hasattr(wrapper, 'get_product') - def test_error_handling(self, market_wrapper): + def test_error_handling(self): try: + market_wrapper = MarketAPIs("USD") fake_product = market_wrapper.get_product("NONEXISTENT_CRYPTO_SYMBOL_12345") assert fake_product is None or fake_product.price == 0 except Exception as e: @@ -140,7 +76,8 @@ class TestMarketSystem: except Exception as e: pass - def test_wrapper_currency_support(self, market_wrapper): + def test_wrapper_currency_support(self): + market_wrapper = MarketAPIs("USD") assert hasattr(market_wrapper, 'currency') assert isinstance(market_wrapper.currency, str) assert len(market_wrapper.currency) >= 3 # USD, EUR, etc. diff --git a/tests/api/test_binance.py b/tests/api/test_binance.py new file mode 100644 index 0000000..0a73c15 --- /dev/null +++ b/tests/api/test_binance.py @@ -0,0 +1,7 @@ +import pytest + +@pytest.mark.market +@pytest.mark.api +class TestBinance: + # TODO fare dei test veri e propri + pass \ No newline at end of file diff --git a/tests/api/test_coinbase.py b/tests/api/test_coinbase.py new file mode 100644 index 0000000..b5f92e8 --- /dev/null +++ b/tests/api/test_coinbase.py @@ -0,0 +1,54 @@ +import os +import pytest +from app.markets import CoinBaseWrapper + +@pytest.mark.market +@pytest.mark.api +@pytest.mark.skipif(not(os.getenv('COINBASE_API_KEY')) or not(os.getenv('COINBASE_API_SECRET')), reason="COINBASE_API_KEY or COINBASE_API_SECRET not set in environment variables") +class TestCoinBase: + + def test_coinbase_init(self): + market = CoinBaseWrapper() + assert market is not None + assert hasattr(market, 'currency') + assert market.currency == "USD" + + def test_coinbase_get_product(self): + market = CoinBaseWrapper() + product = market.get_product("BTC") + assert product is not None + assert hasattr(product, 'symbol') + assert product.symbol == "BTC" + assert hasattr(product, 'price') + assert product.price > 0 + + def test_coinbase_get_products(self): + market = CoinBaseWrapper() + products = market.get_products(["BTC", "ETH"]) + assert products is not None + assert isinstance(products, list) + assert len(products) == 2 + symbols = [p.symbol for p in products] + assert "BTC" in symbols + assert "ETH" in symbols + for product in products: + assert hasattr(product, 'price') + assert product.price > 0 + + def test_coinbase_invalid_product(self): + market = CoinBaseWrapper() + with pytest.raises(Exception): + _ = market.get_product("INVALID") + + def test_coinbase_history(self): + market = CoinBaseWrapper() + history = market.get_historical_prices("BTC", limit=5) + assert history is not None + assert isinstance(history, list) + assert len(history) == 5 + for entry in history: + assert hasattr(entry, 'time') + assert hasattr(entry, 'close') + assert hasattr(entry, 'high') + assert entry.close > 0 + assert entry.high > 0 diff --git a/tests/api/test_cryptocompare.py b/tests/api/test_cryptocompare.py new file mode 100644 index 0000000..52aef9a --- /dev/null +++ b/tests/api/test_cryptocompare.py @@ -0,0 +1,56 @@ +import os +import pytest +from app.markets import CryptoCompareWrapper + +@pytest.mark.market +@pytest.mark.api +@pytest.mark.skipif(not os.getenv('CRYPTOCOMPARE_API_KEY'), reason="CRYPTOCOMPARE_API_KEY not set in environment variables") +class TestCryptoCompare: + + def test_cryptocompare_init(self): + market = CryptoCompareWrapper() + assert market is not None + assert hasattr(market, 'api_key') + assert market.api_key == os.getenv('CRYPTOCOMPARE_API_KEY') + assert hasattr(market, 'currency') + assert market.currency == "USD" + + def test_cryptocompare_get_product(self): + market = CryptoCompareWrapper() + product = market.get_product("BTC") + assert product is not None + assert hasattr(product, 'symbol') + assert product.symbol == "BTC" + assert hasattr(product, 'price') + assert product.price > 0 + + def test_cryptocompare_get_products(self): + market = CryptoCompareWrapper() + products = market.get_products(["BTC", "ETH"]) + assert products is not None + assert isinstance(products, list) + assert len(products) == 2 + symbols = [p.symbol for p in products] + assert "BTC" in symbols + assert "ETH" in symbols + for product in products: + assert hasattr(product, 'price') + assert product.price > 0 + + def test_cryptocompare_invalid_product(self): + market = CryptoCompareWrapper() + with pytest.raises(Exception): + _ = market.get_product("INVALID") + + def test_cryptocompare_history(self): + market = CryptoCompareWrapper() + history = market.get_historical_prices("BTC", limit=5) + assert history is not None + assert isinstance(history, list) + assert len(history) == 5 + for entry in history: + assert hasattr(entry, 'time') + assert hasattr(entry, 'close') + assert hasattr(entry, 'high') + assert entry.close > 0 + assert entry.high > 0 diff --git a/tests/conftest.py b/tests/conftest.py index 40d5aab..e65e86f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -15,8 +15,7 @@ def pytest_configure(config:pytest.Config): markers = [ ("slow", "marks tests as slow (deselect with '-m \"not slow\"')"), ("api", "marks tests that require API access"), - ("coinbase", "marks tests that require Coinbase credentials"), - ("cryptocompare", "marks tests that require CryptoCompare credentials"), + ("market", "marks tests that use market data"), ("gemini", "marks tests that use Gemini model"), ("ollama_gpt", "marks tests that use Ollama GPT model"), ("ollama_qwen", "marks tests that use Ollama Qwen model"), @@ -30,24 +29,7 @@ def pytest_configure(config:pytest.Config): config.addinivalue_line("markers", line) def pytest_collection_modifyitems(config, items): - """Modifica automaticamente gli item di test aggiungendogli marker basati sul nome""" - - markers_to_add = { - "coinbase": pytest.mark.api, - "cryptocompare": pytest.mark.api, - "overview": pytest.mark.slow, - "analysis": pytest.mark.slow, - "gemini": pytest.mark.gemini, - "ollama_gpt": pytest.mark.ollama_gpt, - "ollama_qwen": pytest.mark.ollama_qwen, - } - - for item in items: - name = item.name.lower() - for key, marker in markers_to_add.items(): - if key in name: - item.add_marker(marker) - + """Modifica automaticamente degli item di test rimovendoli""" # Rimuovo i test "limited" e "slow" se non richiesti esplicitamente mark_to_remove = ['limited', 'slow'] for mark in mark_to_remove: