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 165 additions and 109 deletions
Showing only changes of commit a5ef982e12 - Show all commits

View File

@@ -12,7 +12,7 @@ class BaseWrapper:
raise NotImplementedError raise NotImplementedError
def get_all_products(self) -> list['ProductInfo']: def get_all_products(self) -> list['ProductInfo']:
raise NotImplementedError 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 raise NotImplementedError
class ProductInfo(BaseModel): class ProductInfo(BaseModel):

View File

@@ -1,19 +1,32 @@
import os import os
from enum import Enum
from datetime import datetime, timedelta
from coinbase.rest import RESTClient from coinbase.rest import RESTClient
from app.markets.base import ProductInfo, BaseWrapper, Price 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): class CoinBaseWrapper(BaseWrapper):
""" """
Wrapper per le API di Coinbase. 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 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"): def __init__(self, currency: str = "USD"):
if api_key is None: api_key = os.getenv("COINBASE_API_KEY")
api_key = os.getenv("COINBASE_API_KEY")
assert api_key is not None, "API key is required" 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" assert api_private_key is not None, "API private key is required"
self.currency = currency self.currency = currency
@@ -32,14 +45,23 @@ class CoinBaseWrapper(BaseWrapper):
def get_products(self, asset_ids: list[str]) -> list[ProductInfo]: def get_products(self, asset_ids: list[str]) -> list[ProductInfo]:
all_asset_ids = [self.__format(asset_id) for asset_id in asset_ids] 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] return [ProductInfo.from_coinbase(asset) for asset in assets.products]
def get_all_products(self) -> list[ProductInfo]: def get_all_products(self) -> list[ProductInfo]:
assets = self.client.get_products() assets = self.client.get_products()
return [ProductInfo.from_coinbase(asset) for asset in assets.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) 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] return [Price.from_coinbase(candle) for candle in data.candles]

View File

@@ -10,9 +10,8 @@ class CryptoCompareWrapper(BaseWrapper):
La documentazione delle API è disponibile qui: https://developers.coindesk.com/documentation/legacy/Price/SingleSymbolPriceEndpoint 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. !!ATTENZIONE!! sembra essere una API legacy e potrebbe essere deprecata in futuro.
""" """
def __init__(self, api_key:str = None, currency:str='USD'): def __init__(self, currency:str='USD'):
if api_key is None: api_key = os.getenv("CRYPTOCOMPARE_API_KEY")
api_key = os.getenv("CRYPTOCOMPARE_API_KEY")
assert api_key is not None, "API key is required" assert api_key is not None, "API key is required"
self.api_key = api_key self.api_key = api_key
@@ -49,12 +48,11 @@ class CryptoCompareWrapper(BaseWrapper):
def get_all_products(self) -> list[ProductInfo]: def get_all_products(self) -> list[ProductInfo]:
raise NotImplementedError("CryptoCompare does not support fetching all assets") raise NotImplementedError("CryptoCompare does not support fetching all assets")
def get_historical_prices(self, asset_id: str, day_back: int = 10) -> list[dict]: def get_historical_prices(self, asset_id: str, limit: int = 100) -> list[dict]:
assert day_back <= 30, "day_back should be less than or equal to 30"
response = self.__request("/data/v2/histohour", params = { response = self.__request("/data/v2/histohour", params = {
"fsym": asset_id, "fsym": asset_id,
"tsym": self.currency, "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', []) data = response.get('Data', {}).get('Data', [])

View File

@@ -1,34 +1,21 @@
import os import os
import pytest import pytest
from app.agents.market import MarketToolkit 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 from app.markets import MarketAPIs
class TestMarketSystem: class TestMarketSystem:
"""Test suite per il sistema di mercato (wrappers + toolkit)""" """Test suite per il sistema di mercato (wrappers + toolkit)"""
@pytest.fixture(scope="class") def test_wrapper_initialization(self):
def market_wrapper(self) -> BaseWrapper: market_wrapper = MarketAPIs("USD")
return MarketAPIs("USD")
def test_wrapper_initialization(self, market_wrapper):
assert market_wrapper is not None assert market_wrapper is not None
assert hasattr(market_wrapper, 'get_product') assert hasattr(market_wrapper, 'get_product')
assert hasattr(market_wrapper, 'get_products') assert hasattr(market_wrapper, 'get_products')
assert hasattr(market_wrapper, 'get_all_products') assert hasattr(market_wrapper, 'get_all_products')
assert hasattr(market_wrapper, 'get_historical_prices') assert hasattr(market_wrapper, 'get_historical_prices')
def test_providers_configuration(self): def test_wrapper_capabilities(self):
available_providers = [] market_wrapper = MarketAPIs("USD")
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):
capabilities = [] capabilities = []
if hasattr(market_wrapper, 'get_product'): if hasattr(market_wrapper, 'get_product'):
capabilities.append('single_product') capabilities.append('single_product')
@@ -38,14 +25,15 @@ class TestMarketSystem:
capabilities.append('historical_data') capabilities.append('historical_data')
assert len(capabilities) > 0 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") btc_product = market_wrapper.get_product("BTC")
assert btc_product is not None assert btc_product is not None
assert hasattr(btc_product, 'symbol') assert hasattr(btc_product, 'symbol')
assert hasattr(btc_product, 'price') assert hasattr(btc_product, 'price')
assert btc_product.price > 0 assert btc_product.price > 0
def test_market_toolkit_integration(self, market_wrapper): def test_market_toolkit_integration(self):
try: try:
toolkit = MarketToolkit() toolkit = MarketToolkit()
assert toolkit is not None assert toolkit is not None
@@ -59,59 +47,6 @@ class TestMarketSystem:
print(f"MarketToolkit test failed: {e}") print(f"MarketToolkit test failed: {e}")
# Non fail completamente - il toolkit potrebbe avere dipendenze specifiche # 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): def test_provider_selection_mechanism(self):
potential_providers = 0 potential_providers = 0
if os.getenv('CDP_API_KEY_NAME') and os.getenv('CDP_API_PRIVATE_KEY'): 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 wrapper is not None
assert hasattr(wrapper, 'get_product') assert hasattr(wrapper, 'get_product')
def test_error_handling(self, market_wrapper): def test_error_handling(self):
try: try:
market_wrapper = MarketAPIs("USD")
fake_product = market_wrapper.get_product("NONEXISTENT_CRYPTO_SYMBOL_12345") fake_product = market_wrapper.get_product("NONEXISTENT_CRYPTO_SYMBOL_12345")
assert fake_product is None or fake_product.price == 0 assert fake_product is None or fake_product.price == 0
except Exception as e: except Exception as e:
@@ -140,7 +76,8 @@ class TestMarketSystem:
except Exception as e: except Exception as e:
pass 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 hasattr(market_wrapper, 'currency')
assert isinstance(market_wrapper.currency, str) assert isinstance(market_wrapper.currency, str)
assert len(market_wrapper.currency) >= 3 # USD, EUR, etc. assert len(market_wrapper.currency) >= 3 # USD, EUR, etc.

View File

@@ -0,0 +1,7 @@
import pytest
@pytest.mark.market
@pytest.mark.api
class TestBinance:
# TODO fare dei test veri e propri
pass

View File

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

View File

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

View File

@@ -15,8 +15,7 @@ def pytest_configure(config:pytest.Config):
markers = [ markers = [
("slow", "marks tests as slow (deselect with '-m \"not slow\"')"), ("slow", "marks tests as slow (deselect with '-m \"not slow\"')"),
("api", "marks tests that require API access"), ("api", "marks tests that require API access"),
("coinbase", "marks tests that require Coinbase credentials"), ("market", "marks tests that use market data"),
("cryptocompare", "marks tests that require CryptoCompare credentials"),
("gemini", "marks tests that use Gemini model"), ("gemini", "marks tests that use Gemini model"),
("ollama_gpt", "marks tests that use Ollama GPT model"), ("ollama_gpt", "marks tests that use Ollama GPT model"),
("ollama_qwen", "marks tests that use Ollama Qwen 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) config.addinivalue_line("markers", line)
def pytest_collection_modifyitems(config, items): def pytest_collection_modifyitems(config, items):
"""Modifica automaticamente gli item di test aggiungendogli marker basati sul nome""" """Modifica automaticamente degli item di test rimovendoli"""
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)
# Rimuovo i test "limited" e "slow" se non richiesti esplicitamente # Rimuovo i test "limited" e "slow" se non richiesti esplicitamente
mark_to_remove = ['limited', 'slow'] mark_to_remove = ['limited', 'slow']
for mark in mark_to_remove: for mark in mark_to_remove: