9 enhancement con financialdatasettool e yfinance #11
@@ -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):
|
||||
|
||||
@@ -1,18 +1,31 @@
|
||||
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:
|
||||
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")
|
||||
assert api_private_key is not None, "API private key is required"
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -10,8 +10,7 @@ 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:
|
||||
def __init__(self, currency:str='USD'):
|
||||
api_key = os.getenv("CRYPTOCOMPARE_API_KEY")
|
||||
assert api_key is not None, "API key is required"
|
||||
|
||||
@@ -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', [])
|
||||
|
||||
@@ -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.
|
||||
|
||||
7
tests/api/test_binance.py
Normal file
7
tests/api/test_binance.py
Normal file
@@ -0,0 +1,7 @@
|
||||
import pytest
|
||||
|
||||
@pytest.mark.market
|
||||
@pytest.mark.api
|
||||
class TestBinance:
|
||||
# TODO fare dei test veri e propri
|
||||
pass
|
||||
54
tests/api/test_coinbase.py
Normal file
54
tests/api/test_coinbase.py
Normal 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
|
||||
56
tests/api/test_cryptocompare.py
Normal file
56
tests/api/test_cryptocompare.py
Normal 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
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user