diff --git a/src/app/markets/__init__.py b/src/app/markets/__init__.py index 6f09b2f..b782b8f 100644 --- a/src/app/markets/__init__.py +++ b/src/app/markets/__init__.py @@ -82,7 +82,25 @@ class MarketAPIsTool(BaseWrapper, Toolkit): all_prices = self.wrappers.try_call_all(lambda w: w.get_historical_prices(asset_id, limit)) return aggregate_history_prices(all_prices) -# TODO definire istruzioni per gli agenti di mercato MARKET_INSTRUCTIONS = """ +**TASK:** You are a specialized **Crypto Price Data Retrieval Agent**. Your primary goal is to fetch the most recent and/or historical price data for requested cryptocurrency assets (e.g., 'BTC', 'ETH', 'SOL'). You must provide the data in a clear and structured format. +**AVAILABLE TOOLS:** +1. `get_products(asset_ids: list[str])`: Get **current** product/price info for a list of assets. **(PREFERITA: usa questa per i prezzi live)** +2. `get_historical_prices(asset_id: str, limit: int)`: Get historical price data for one asset. Default limit is 100. **(PREFERITA: usa questa per i dati storici)** +3. `get_products_aggregated(asset_ids: list[str])`: Get **aggregated current** product/price info for a list of assets. **(USA SOLO SE richiesto 'aggregato' o se `get_products` fallisce)** +4. `get_historical_prices_aggregated(asset_id: str, limit: int)`: Get **aggregated historical** price data for one asset. **(USA SOLO SE richiesto 'aggregato' o se `get_historical_prices` fallisce)** + +**USAGE GUIDELINE:** +* **Asset ID:** Always convert common names (e.g., 'Bitcoin', 'Ethereum') into their official ticker/ID (e.g., 'BTC', 'ETH'). +* **Cost Management (Cruciale per LLM locale):** + * **Priorità Bassa per Aggregazione:** **Non** usare i metodi `*aggregated` a meno che l'utente non lo richieda esplicitamente o se i metodi non-aggregati falliscono. + * **Limitazione Storica:** Il limite predefinito per i dati storici deve essere **20** punti dati, a meno che l'utente non specifichi un limite diverso. +* **Fallimento Tool:** Se lo strumento non restituisce dati per un asset specifico, rispondi per quell'asset con: "Dati di prezzo non trovati per [Asset ID]." + +**REPORTING REQUIREMENT:** +1. **Format:** Output the results in a clear, easy-to-read list or table. +2. **Live Price Request:** If an asset's *current price* is requested, report the **Asset ID**, **Latest Price**, and **Time/Date of the price**. +3. **Historical Price Request:** If *historical data* is requested, report the **Asset ID**, the **Limit** of points returned, and the **First** and **Last** entries from the list of historical prices (Date, Price). Non stampare l'intera lista di dati storici. +4. **Output:** For all requests, fornire un **unico e conciso riepilogo** dei dati reperiti. """ \ No newline at end of file diff --git a/src/app/markets/base.py b/src/app/markets/base.py index 4224014..1ef247b 100644 --- a/src/app/markets/base.py +++ b/src/app/markets/base.py @@ -14,7 +14,7 @@ class BaseWrapper: Returns: ProductInfo: An object containing product information. """ - raise NotImplementedError + raise NotImplementedError("This method should be overridden by subclasses") def get_products(self, asset_ids: list[str]) -> list['ProductInfo']: """ @@ -24,7 +24,7 @@ class BaseWrapper: Returns: list[ProductInfo]: A list of objects containing product information. """ - raise NotImplementedError + raise NotImplementedError("This method should be overridden by subclasses") def get_historical_prices(self, asset_id: str = "BTC", limit: int = 100) -> list['Price']: """ @@ -35,7 +35,7 @@ class BaseWrapper: Returns: list[Price]: A list of Price objects. """ - raise NotImplementedError + raise NotImplementedError("This method should be overridden by subclasses") class ProductInfo(BaseModel): """ diff --git a/src/app/utils/market_aggregation.py b/src/app/utils/market_aggregation.py index bb6c6d4..d0cdfb3 100644 --- a/src/app/utils/market_aggregation.py +++ b/src/app/utils/market_aggregation.py @@ -4,10 +4,12 @@ from app.markets.base import ProductInfo, Price def aggregate_history_prices(prices: dict[str, list[Price]]) -> list[Price]: """ - Aggrega i prezzi storici per symbol calcolando la media - + Aggrega i prezzi storici per symbol calcolando la media oraria. + Args: + prices (dict[str, list[Price]]): Mappa provider -> lista di Price + Returns: + list[Price]: Lista di Price aggregati per ora """ - max_list_length = max(len(p) for p in prices.values()) # Costruiamo una mappa timestamp_h -> lista di Price timestamped_prices: dict[int, list[Price]] = {} @@ -27,13 +29,15 @@ def aggregate_history_prices(prices: dict[str, list[Price]]) -> list[Price]: price.close = statistics.mean([p.close for p in price_list]) price.volume = statistics.mean([p.volume for p in price_list]) aggregated_prices.append(price) - - assert(len(aggregated_prices) <= max_list_length) return aggregated_prices def aggregate_product_info(products: dict[str, list[ProductInfo]]) -> list[ProductInfo]: """ Aggrega una lista di ProductInfo per symbol. + Args: + products (dict[str, list[ProductInfo]]): Mappa provider -> lista di ProductInfo + Returns: + list[ProductInfo]: Lista di ProductInfo aggregati per symbol """ # Costruzione mappa symbol -> lista di ProductInfo @@ -48,15 +52,16 @@ def aggregate_product_info(products: dict[str, list[ProductInfo]]) -> list[Produ for symbol, product_list in symbols_infos.items(): product = ProductInfo() - product.id = f"{symbol}_AGG" + product.id = f"{symbol}_AGGREGATED" product.symbol = symbol product.quote_currency = next(p.quote_currency for p in product_list if p.quote_currency) - prices = [p.price for p in product_list] - product.price = statistics.mean(prices) + volume_sum = sum(p.volume_24h for p in product_list) + product.volume_24h = volume_sum / len(product_list) if product_list else 0.0 + + prices = sum(p.price * p.volume_24h for p in product_list) + product.price = (prices / volume_sum) if volume_sum > 0 else 0.0 - volumes = [p.volume_24h for p in product_list] - product.volume_24h = sum([p * v for p, v in zip(prices, volumes)]) / sum(volumes) aggregated_products.append(product) confidence = _calculate_confidence(product_list, sources) # TODO necessary? diff --git a/tests/utils/test_market_aggregator.py b/tests/utils/test_market_aggregator.py index f641ee5..4f5611c 100644 --- a/tests/utils/test_market_aggregator.py +++ b/tests/utils/test_market_aggregator.py @@ -40,10 +40,10 @@ class TestMarketDataAggregator: info = aggregated[0] assert info is not None assert info.symbol == "BTC" - assert info.price == pytest.approx(50000.0, rel=1e-3) - avg_weighted_volume = (50000.0 * 1000.0 + 50100.0 * 1100.0 + 49900.0 * 900.0) / (1000.0 + 1100.0 + 900.0) - assert info.volume_24h == pytest.approx(avg_weighted_volume, rel=1e-3) + avg_weighted_price = (50000.0 * 1000.0 + 50100.0 * 1100.0 + 49900.0 * 900.0) / (1000.0 + 1100.0 + 900.0) + assert info.price == pytest.approx(avg_weighted_price, rel=1e-3) + assert info.volume_24h == pytest.approx(1000.0, rel=1e-3) assert info.quote_currency == "USD" def test_aggregate_product_info_multiple_symbols(self): @@ -65,17 +65,38 @@ class TestMarketDataAggregator: eth_info = next((p for p in aggregated if p.symbol == "ETH"), None) assert btc_info is not None - assert btc_info.price == pytest.approx(50050.0, rel=1e-3) - avg_weighted_volume_btc = (50000.0 * 1000.0 + 50100.0 * 1100.0) / (1000.0 + 1100.0) - assert btc_info.volume_24h == pytest.approx(avg_weighted_volume_btc, rel=1e-3) + avg_weighted_price_btc = (50000.0 * 1000.0 + 50100.0 * 1100.0) / (1000.0 + 1100.0) + assert btc_info.price == pytest.approx(avg_weighted_price_btc, rel=1e-3) + assert btc_info.volume_24h == pytest.approx(1050.0, rel=1e-3) assert btc_info.quote_currency == "USD" assert eth_info is not None - assert eth_info.price == pytest.approx(4025.0, rel=1e-3) - avg_weighted_volume_eth = (4000.0 * 2000.0 + 4050.0 * 2100.0) / (2000.0 + 2100.0) - assert eth_info.volume_24h == pytest.approx(avg_weighted_volume_eth, rel=1e-3) + avg_weighted_price_eth = (4000.0 * 2000.0 + 4050.0 * 2100.0) / (2000.0 + 2100.0) + assert eth_info.price == pytest.approx(avg_weighted_price_eth, rel=1e-3) + assert eth_info.volume_24h == pytest.approx(2050.0, rel=1e-3) assert eth_info.quote_currency == "USD" + def test_aggregate_product_info_with_no_data(self): + products = { + "Provider1": [], + "Provider2": [], + } + aggregated = aggregate_product_info(products) + assert len(aggregated) == 0 + + def test_aggregate_product_info_with_partial_data(self): + products = { + "Provider1": [self.__product("BTC", 50000.0, 1000.0, "USD")], + "Provider2": [], + } + aggregated = aggregate_product_info(products) + assert len(aggregated) == 1 + info = aggregated[0] + assert info.symbol == "BTC" + assert info.price == pytest.approx(50000.0, rel=1e-3) + assert info.volume_24h == pytest.approx(1000.0, rel=1e-3) + assert info.quote_currency == "USD" + def test_aggregate_history_prices(self): """Test aggregazione di prezzi storici usando aggregate_history_prices"""