12 fix docs #13

Merged
Berack96 merged 18 commits from 12-fix-docs into main 2025-10-02 01:41:00 +02:00
2 changed files with 41 additions and 209 deletions
Showing only changes of commit 9c471948ff - Show all commits

View File

@@ -3,63 +3,30 @@ from agno.tools.yfinance import YFinanceTools
from .base import BaseWrapper, ProductInfo, Price from .base import BaseWrapper, ProductInfo, Price
copilot-pull-request-reviewer[bot] commented 2025-10-02 01:29:51 +02:00 (Migrated from github.com)
Review

The comment says the currency is after the '-' but the code takes the part before it (index 0). This logic appears incorrect - for 'BTC-USD', this would set quote_currency to 'BTC' instead of 'USD'.

    product.quote_currency = product.id.split('-')[1] if '-' in product.id else ''  # La valuta è la parte dopo il '-'
The comment says the currency is after the '-' but the code takes the part before it (index 0). This logic appears incorrect - for 'BTC-USD', this would set quote_currency to 'BTC' instead of 'USD'. ```suggestion product.quote_currency = product.id.split('-')[1] if '-' in product.id else '' # La valuta è la parte dopo il '-' ```
def create_product_info(symbol: str, stock_data: dict) -> ProductInfo: def create_product_info(stock_data: dict[str, str]) -> ProductInfo:
""" """
copilot-pull-request-reviewer[bot] commented 2025-10-02 01:37:57 +02:00 (Migrated from github.com)
Review

The type annotation dict[str, str] is too restrictive. The function expects numeric values for price calculations but the annotation suggests all values are strings, which could lead to type errors during float conversion.

The type annotation dict[str, str] is too restrictive. The function expects numeric values for price calculations but the annotation suggests all values are strings, which could lead to type errors during float conversion.
Converte i dati di YFinanceTools in ProductInfo. Converte i dati di YFinanceTools in ProductInfo.
""" """
product = ProductInfo() product = ProductInfo()
product.id = stock_data.get('Symbol', '')
# ID univoco per yfinance product.symbol = product.id.split('-')[0] # Rimuovi il suffisso della valuta per le crypto
product.id = f"yfinance_{symbol}" product.price = float(stock_data.get('Current Stock Price', f"0.0 USD").split(" ")[0]) # prende solo il numero
product.symbol = symbol product.volume_24h = 0.0 # YFinance non fornisce il volume 24h direttamente
# Estrai il prezzo corrente - gestisci diversi formati
if 'currentPrice' in stock_data:
product.price = float(stock_data['currentPrice'])
elif 'regularMarketPrice' in stock_data:
product.price = float(stock_data['regularMarketPrice'])
elif 'Current Stock Price' in stock_data:
# Formato: "254.63 USD" - estrai solo il numero
price_str = stock_data['Current Stock Price'].split()[0]
try:
product.price = float(price_str)
except ValueError:
product.price = 0.0
else:
product.price = 0.0
# Volume 24h
if 'volume' in stock_data:
product.volume_24h = float(stock_data['volume'])
elif 'regularMarketVolume' in stock_data:
product.volume_24h = float(stock_data['regularMarketVolume'])
else:
product.volume_24h = 0.0
# Status basato sulla disponibilità dei dati
product.status = "trading" if product.price > 0 else "offline" product.status = "trading" if product.price > 0 else "offline"
product.quote_currency = product.id.split('-')[0] # La valuta è la parte dopo il '-'
# Valuta (default USD)
product.quote_currency = stock_data.get('currency', 'USD') or 'USD'
return product return product
def create_price_from_history(hist_data: dict[str, str]) -> Price:
def create_price_from_history(hist_data: dict, timestamp: str) -> Price:
""" """
Converte i dati storici di YFinanceTools in Price. Converte i dati storici di YFinanceTools in Price.
""" """
price = Price() price = Price()
price.high = float(hist_data.get('High', 0.0))
if timestamp in hist_data: price.low = float(hist_data.get('Low', 0.0))
day_data = hist_data[timestamp] price.open = float(hist_data.get('Open', 0.0))
price.high = float(day_data.get('High', 0.0)) price.close = float(hist_data.get('Close', 0.0))
price.low = float(day_data.get('Low', 0.0)) price.volume = float(hist_data.get('Volume', 0.0))
price.open = float(day_data.get('Open', 0.0)) price.time = hist_data.get('Timestamp', '')
price.close = float(day_data.get('Close', 0.0))
price.volume = float(day_data.get('Volume', 0.0))
price.time = timestamp
return price return price
@@ -72,143 +39,46 @@ class YFinanceWrapper(BaseWrapper):
def __init__(self, currency: str = "USD"): def __init__(self, currency: str = "USD"):
self.currency = currency self.currency = currency
# Inizializza YFinanceTools - non richiede parametri specifici
self.tool = YFinanceTools() self.tool = YFinanceTools()
def _format_symbol(self, asset_id: str) -> str: def _format_symbol(self, asset_id: str) -> str:
""" """
Formatta il simbolo per yfinance. Formatta il simbolo per yfinance.
Per crypto, aggiunge '-USD' se non presente. Per crypto, aggiunge '-' e la valuta (es. BTC -> BTC-USD).
""" """
asset_id = asset_id.upper() asset_id = asset_id.upper()
return f"{asset_id}-{self.currency}" if '-' not in asset_id else asset_id
# Se è già nel formato corretto (es: BTC-USD), usa così
if '-' in asset_id:
return asset_id
# Per crypto singole (BTC, ETH), aggiungi -USD
if asset_id in ['BTC', 'ETH', 'ADA', 'SOL', 'DOT', 'LINK', 'UNI', 'AAVE']:
return f"{asset_id}-USD"
# Per azioni, usa il simbolo così com'è
return asset_id
def get_product(self, asset_id: str) -> ProductInfo: def get_product(self, asset_id: str) -> ProductInfo:
"""
Recupera le informazioni di un singolo prodotto.
"""
symbol = self._format_symbol(asset_id) symbol = self._format_symbol(asset_id)
stock_info = self.tool.get_company_info(symbol)
# Usa YFinanceTools per ottenere i dati stock_info = json.loads(stock_info)
try: return create_product_info(stock_info)
# Ottieni le informazioni base dello stock
stock_info = self.tool.get_company_info(symbol)
# Se il risultato è una stringa JSON, parsala
if isinstance(stock_info, str):
try:
stock_data = json.loads(stock_info)
except json.JSONDecodeError:
# Se non è JSON valido, prova a ottenere solo il prezzo
price_data_str = self.tool.get_current_stock_price(symbol)
if price_data_str and price_data_str.replace('.', '').replace('-', '').isdigit():
price = float(price_data_str)
stock_data = {'currentPrice': price, 'currency': 'USD'}
else:
raise Exception("Dati non validi")
else:
stock_data = stock_info
return create_product_info(symbol, stock_data)
except Exception as e:
# Fallback: prova a ottenere solo il prezzo
try:
price_data_str = self.tool.get_current_stock_price(symbol)
if price_data_str and price_data_str.replace('.', '').replace('-', '').isdigit():
price = float(price_data_str)
minimal_data = {
'currentPrice': price,
'currency': 'USD'
}
return create_product_info(symbol, minimal_data)
else:
raise Exception("Prezzo non disponibile")
except Exception:
# Se tutto fallisce, restituisci un prodotto vuoto
product = ProductInfo()
product.symbol = symbol
product.status = "offline"
return product
def get_products(self, asset_ids: list[str]) -> list[ProductInfo]: def get_products(self, asset_ids: list[str]) -> list[ProductInfo]:
"""
Recupera le informazioni di multiple assets.
"""
products = [] products = []
for asset_id in asset_ids: for asset_id in asset_ids:
try: product = self.get_product(asset_id)
product = self.get_product(asset_id) products.append(product)
products.append(product)
except Exception as e:
# Se un asset non è disponibile, continua con gli altri
continue
return products return products
def get_all_products(self) -> list[ProductInfo]: def get_all_products(self) -> list[ProductInfo]:
""" raise NotImplementedError("YFinanceWrapper does not support get_all_products due to API limitations.")
Recupera tutti i prodotti disponibili.
Restituisce una lista predefinita di asset popolari.
"""
# Lista di asset popolari (azioni, ETF, crypto)
popular_assets = [
'BTC', 'ETH', 'ADA', 'SOL', 'DOT',
'AAPL', 'GOOGL', 'MSFT', 'TSLA', 'AMZN',
'SPY', 'QQQ', 'VTI', 'GLD', 'VIX'
]
return self.get_products(popular_assets)
def get_historical_prices(self, asset_id: str = "BTC", limit: int = 100) -> list[Price]: def get_historical_prices(self, asset_id: str = "BTC", limit: int = 100) -> list[Price]:
"""
Recupera i dati storici di prezzo per un asset.
"""
symbol = self._format_symbol(asset_id) symbol = self._format_symbol(asset_id)
try: days = limit // 24 + 1 # Arrotonda per eccesso
# Determina il periodo appropriato in base al limite hist_data = self.tool.get_historical_stock_prices(symbol, period=f"{days}d", interval="1h")
if limit <= 7: hist_data = json.loads(hist_data)
period = "1d"
interval = "15m"
elif limit <= 30:
period = "5d"
interval = "1h"
elif limit <= 90:
period = "1mo"
interval = "1d"
else:
period = "3mo"
interval = "1d"
# Ottieni i dati storici # Il formato dei dati è {timestamp: {Open: x, High: y, Low: z, Close: w, Volume: v}}
hist_data = self.tool.get_historical_stock_prices(symbol, period=period, interval=interval) timestamps = sorted(hist_data.keys())[-limit:]
if isinstance(hist_data, str): prices = []
hist_data = json.loads(hist_data) for timestamp in timestamps:
temp = hist_data[timestamp]
# Il formato dei dati è {timestamp: {Open: x, High: y, Low: z, Close: w, Volume: v}} temp['Timestamp'] = timestamp
prices = [] price = create_price_from_history(temp)
timestamps = sorted(hist_data.keys())[-limit:] # Prendi gli ultimi 'limit' timestamp prices.append(price)
return prices
for timestamp in timestamps:
price = create_price_from_history(hist_data, timestamp)
if price.close > 0: # Solo se ci sono dati validi
prices.append(price)
return prices
except Exception as e:
# Se fallisce, restituisci lista vuota
return []

View File

@@ -1,4 +1,3 @@
import os
import pytest import pytest
from app.markets import YFinanceWrapper from app.markets import YFinanceWrapper
@@ -14,17 +13,6 @@ class TestYFinance:
assert hasattr(market, 'tool') assert hasattr(market, 'tool')
assert market.tool is not None assert market.tool is not None
def test_yfinance_get_product(self):
market = YFinanceWrapper()
product = market.get_product("AAPL")
assert product is not None
assert hasattr(product, 'symbol')
assert product.symbol == "AAPL"
assert hasattr(product, 'price')
assert product.price > 0
assert hasattr(product, 'status')
assert product.status == "trading"
def test_yfinance_get_crypto_product(self): def test_yfinance_get_crypto_product(self):
market = YFinanceWrapper() market = YFinanceWrapper()
product = market.get_product("BTC") product = market.get_product("BTC")
@@ -37,49 +25,21 @@ class TestYFinance:
def test_yfinance_get_products(self): def test_yfinance_get_products(self):
market = YFinanceWrapper() market = YFinanceWrapper()
products = market.get_products(["AAPL", "GOOGL"]) products = market.get_products(["BTC", "ETH"])
assert products is not None assert products is not None
assert isinstance(products, list) assert isinstance(products, list)
assert len(products) == 2 assert len(products) == 2
symbols = [p.symbol for p in products] symbols = [p.symbol for p in products]
assert "AAPL" in symbols assert "BTC" in symbols
assert "GOOGL" in symbols assert "ETH" in symbols
for product in products: for product in products:
assert hasattr(product, 'price') assert hasattr(product, 'price')
assert product.price > 0 assert product.price > 0
def test_yfinance_get_all_products(self):
market = YFinanceWrapper()
products = market.get_all_products()
assert products is not None
assert isinstance(products, list)
assert len(products) > 0
# Dovrebbe contenere asset popolari
symbols = [p.symbol for p in products]
assert "AAPL" in symbols # Apple dovrebbe essere nella lista
for product in products:
assert hasattr(product, 'symbol')
assert hasattr(product, 'price')
def test_yfinance_invalid_product(self): def test_yfinance_invalid_product(self):
market = YFinanceWrapper() market = YFinanceWrapper()
# Per YFinance, un prodotto invalido dovrebbe restituire un prodotto offline with pytest.raises(Exception):
product = market.get_product("INVALIDSYMBOL123") _ = market.get_product("INVALIDSYMBOL123")
assert product is not None
assert product.status == "offline"
def test_yfinance_history(self):
market = YFinanceWrapper()
history = market.get_historical_prices("AAPL", 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
def test_yfinance_crypto_history(self): def test_yfinance_crypto_history(self):
market = YFinanceWrapper() market = YFinanceWrapper()
@@ -90,4 +50,6 @@ class TestYFinance:
for entry in history: for entry in history:
assert hasattr(entry, 'time') assert hasattr(entry, 'time')
assert hasattr(entry, 'close') assert hasattr(entry, 'close')
assert entry.close > 0 assert entry.close > 0
assert hasattr(entry, 'open')
assert entry.open > 0