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
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.
"""
product = ProductInfo()
# ID univoco per yfinance
product.id = f"yfinance_{symbol}"
product.symbol = symbol
# 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.id = stock_data.get('Symbol', '')
product.symbol = product.id.split('-')[0] # Rimuovi il suffisso della valuta per le crypto
product.price = float(stock_data.get('Current Stock Price', f"0.0 USD").split(" ")[0]) # prende solo il numero
product.volume_24h = 0.0 # YFinance non fornisce il volume 24h direttamente
product.status = "trading" if product.price > 0 else "offline"
# Valuta (default USD)
product.quote_currency = stock_data.get('currency', 'USD') or 'USD'
product.quote_currency = product.id.split('-')[0] # La valuta è la parte dopo il '-'
return product
def create_price_from_history(hist_data: dict, timestamp: str) -> Price:
def create_price_from_history(hist_data: dict[str, str]) -> Price:
"""
Converte i dati storici di YFinanceTools in Price.
"""
price = Price()
if timestamp in hist_data:
day_data = hist_data[timestamp]
price.high = float(day_data.get('High', 0.0))
price.low = float(day_data.get('Low', 0.0))
price.open = float(day_data.get('Open', 0.0))
price.close = float(day_data.get('Close', 0.0))
price.volume = float(day_data.get('Volume', 0.0))
price.time = timestamp
price.high = float(hist_data.get('High', 0.0))
price.low = float(hist_data.get('Low', 0.0))
price.open = float(hist_data.get('Open', 0.0))
price.close = float(hist_data.get('Close', 0.0))
price.volume = float(hist_data.get('Volume', 0.0))
price.time = hist_data.get('Timestamp', '')
return price
@@ -72,143 +39,46 @@ class YFinanceWrapper(BaseWrapper):
def __init__(self, currency: str = "USD"):
self.currency = currency
# Inizializza YFinanceTools - non richiede parametri specifici
self.tool = YFinanceTools()
def _format_symbol(self, asset_id: str) -> str:
"""
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()
# 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
return f"{asset_id}-{self.currency}" if '-' not in asset_id else asset_id
def get_product(self, asset_id: str) -> ProductInfo:
"""
Recupera le informazioni di un singolo prodotto.
"""
symbol = self._format_symbol(asset_id)
# Usa YFinanceTools per ottenere i dati
try:
# 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
stock_info = self.tool.get_company_info(symbol)
stock_info = json.loads(stock_info)
return create_product_info(stock_info)
def get_products(self, asset_ids: list[str]) -> list[ProductInfo]:
"""
Recupera le informazioni di multiple assets.
"""
products = []
for asset_id in asset_ids:
try:
product = self.get_product(asset_id)
products.append(product)
except Exception as e:
# Se un asset non è disponibile, continua con gli altri
continue
product = self.get_product(asset_id)
products.append(product)
return products
def get_all_products(self) -> list[ProductInfo]:
"""
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)
raise NotImplementedError("YFinanceWrapper does not support get_all_products due to API limitations.")
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)
try:
# Determina il periodo appropriato in base al limite
if limit <= 7:
period = "1d"
interval = "15m"
elif limit <= 30:
period = "5d"
interval = "1h"
elif limit <= 90:
period = "1mo"
interval = "1d"
else:
period = "3mo"
interval = "1d"
days = limit // 24 + 1 # Arrotonda per eccesso
hist_data = self.tool.get_historical_stock_prices(symbol, period=f"{days}d", interval="1h")
hist_data = json.loads(hist_data)
# Ottieni i dati storici
hist_data = self.tool.get_historical_stock_prices(symbol, period=period, interval=interval)
# Il formato dei dati è {timestamp: {Open: x, High: y, Low: z, Close: w, Volume: v}}
timestamps = sorted(hist_data.keys())[-limit:]
if isinstance(hist_data, str):
hist_data = json.loads(hist_data)
# Il formato dei dati è {timestamp: {Open: x, High: y, Low: z, Close: w, Volume: v}}
prices = []
timestamps = sorted(hist_data.keys())[-limit:] # Prendi gli ultimi 'limit' timestamp
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 []
prices = []
for timestamp in timestamps:
temp = hist_data[timestamp]
temp['Timestamp'] = timestamp
price = create_price_from_history(temp)
prices.append(price)
return prices

View File

@@ -1,4 +1,3 @@
import os
import pytest
from app.markets import YFinanceWrapper
@@ -14,17 +13,6 @@ class TestYFinance:
assert hasattr(market, 'tool')
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):
market = YFinanceWrapper()
product = market.get_product("BTC")
@@ -37,49 +25,21 @@ class TestYFinance:
def test_yfinance_get_products(self):
market = YFinanceWrapper()
products = market.get_products(["AAPL", "GOOGL"])
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 "AAPL" in symbols
assert "GOOGL" in symbols
assert "BTC" in symbols
assert "ETH" in symbols
for product in products:
assert hasattr(product, 'price')
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):
market = YFinanceWrapper()
# Per YFinance, un prodotto invalido dovrebbe restituire un prodotto offline
product = 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
with pytest.raises(Exception):
_ = market.get_product("INVALIDSYMBOL123")
def test_yfinance_crypto_history(self):
market = YFinanceWrapper()
@@ -90,4 +50,6 @@ class TestYFinance:
for entry in history:
assert hasattr(entry, 'time')
assert hasattr(entry, 'close')
assert entry.close > 0
assert entry.close > 0
assert hasattr(entry, 'open')
assert entry.open > 0