12 fix docs #13

Merged
Berack96 merged 18 commits from 12-fix-docs into main 2025-10-02 01:41:00 +02:00
4 changed files with 67 additions and 23 deletions
Showing only changes of commit 8b81cd5a71 - Show all commits

View File

@@ -82,7 +82,25 @@ class MarketAPIsTool(BaseWrapper, Toolkit):
all_prices = self.wrappers.try_call_all(lambda w: w.get_historical_prices(asset_id, limit)) all_prices = self.wrappers.try_call_all(lambda w: w.get_historical_prices(asset_id, limit))
return aggregate_history_prices(all_prices) return aggregate_history_prices(all_prices)
# TODO definire istruzioni per gli agenti di mercato
MARKET_INSTRUCTIONS = """ 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.
""" """

View File

@@ -14,7 +14,7 @@ class BaseWrapper:
Returns: Returns:
ProductInfo: An object containing product information. 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']: def get_products(self, asset_ids: list[str]) -> list['ProductInfo']:
""" """
@@ -24,7 +24,7 @@ class BaseWrapper:
Returns: Returns:
list[ProductInfo]: A list of objects containing product information. 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']: def get_historical_prices(self, asset_id: str = "BTC", limit: int = 100) -> list['Price']:
""" """
@@ -35,7 +35,7 @@ class BaseWrapper:
Returns: Returns:
list[Price]: A list of Price objects. list[Price]: A list of Price objects.
""" """
raise NotImplementedError raise NotImplementedError("This method should be overridden by subclasses")
class ProductInfo(BaseModel): class ProductInfo(BaseModel):
""" """

View File

@@ -4,10 +4,12 @@ from app.markets.base import ProductInfo, Price
def aggregate_history_prices(prices: dict[str, list[Price]]) -> list[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 # Costruiamo una mappa timestamp_h -> lista di Price
timestamped_prices: dict[int, list[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.close = statistics.mean([p.close for p in price_list])
price.volume = statistics.mean([p.volume for p in price_list]) price.volume = statistics.mean([p.volume for p in price_list])
aggregated_prices.append(price) aggregated_prices.append(price)
assert(len(aggregated_prices) <= max_list_length)
return aggregated_prices return aggregated_prices
def aggregate_product_info(products: dict[str, list[ProductInfo]]) -> list[ProductInfo]: def aggregate_product_info(products: dict[str, list[ProductInfo]]) -> list[ProductInfo]:
""" """
Aggrega una lista di ProductInfo per symbol. 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 # 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(): for symbol, product_list in symbols_infos.items():
product = ProductInfo() product = ProductInfo()
product.id = f"{symbol}_AGG" product.id = f"{symbol}_AGGREGATED"
product.symbol = symbol product.symbol = symbol
product.quote_currency = next(p.quote_currency for p in product_list if p.quote_currency) product.quote_currency = next(p.quote_currency for p in product_list if p.quote_currency)
prices = [p.price for p in product_list] volume_sum = sum(p.volume_24h for p in product_list)
product.price = statistics.mean(prices) 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) aggregated_products.append(product)
confidence = _calculate_confidence(product_list, sources) # TODO necessary? confidence = _calculate_confidence(product_list, sources) # TODO necessary?

View File

@@ -40,10 +40,10 @@ class TestMarketDataAggregator:
info = aggregated[0] info = aggregated[0]
assert info is not None assert info is not None
assert info.symbol == "BTC" 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) avg_weighted_price = (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) 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" assert info.quote_currency == "USD"
def test_aggregate_product_info_multiple_symbols(self): 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) eth_info = next((p for p in aggregated if p.symbol == "ETH"), None)
assert btc_info is not None assert btc_info is not None
assert btc_info.price == pytest.approx(50050.0, rel=1e-3) avg_weighted_price_btc = (50000.0 * 1000.0 + 50100.0 * 1100.0) / (1000.0 + 1100.0)
avg_weighted_volume_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(avg_weighted_volume_btc, rel=1e-3) assert btc_info.volume_24h == pytest.approx(1050.0, rel=1e-3)
assert btc_info.quote_currency == "USD" assert btc_info.quote_currency == "USD"
assert eth_info is not None assert eth_info is not None
assert eth_info.price == pytest.approx(4025.0, rel=1e-3) avg_weighted_price_eth = (4000.0 * 2000.0 + 4050.0 * 2100.0) / (2000.0 + 2100.0)
avg_weighted_volume_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(avg_weighted_volume_eth, rel=1e-3) assert eth_info.volume_24h == pytest.approx(2050.0, rel=1e-3)
assert eth_info.quote_currency == "USD" 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): def test_aggregate_history_prices(self):
"""Test aggregazione di prezzi storici usando aggregate_history_prices""" """Test aggregazione di prezzi storici usando aggregate_history_prices"""