diff --git a/src/app/api/core/markets.py b/src/app/api/core/markets.py index 34c788e..e5fe647 100644 --- a/src/app/api/core/markets.py +++ b/src/app/api/core/markets.py @@ -34,33 +34,28 @@ class ProductInfo(BaseModel): product.provider = provider_name symbols_infos.setdefault(product.symbol, []).append(product) - # Aggregazione per ogni symbol + # Aggregazione per ogni symbol usando aggregate_single_asset aggregated_products: list[ProductInfo] = [] for symbol, product_list in symbols_infos.items(): - product = ProductInfo() - - product.id = f"{symbol}_AGGREGATED" - product.symbol = symbol - product.currency = next((p.currency for p in product_list if p.currency), "") - - # Raccogliamo i provider che hanno fornito dati - providers = [p.provider for p in product_list if p.provider] - product.provider = ", ".join(set(providers)) if providers else "AGGREGATED" - - # Calcolo del volume medio - volume_sum = sum(p.volume_24h for p in product_list if p.volume_24h > 0) - product.volume_24h = volume_sum / len(product_list) if product_list else 0.0 - - # Calcolo del prezzo pesato per volume (VWAP - Volume Weighted Average Price) - if volume_sum > 0: - prices_weighted = sum(p.price * p.volume_24h for p in product_list if p.volume_24h > 0) - product.price = prices_weighted / volume_sum - else: - # Se non c'รจ volume, facciamo una media semplice dei prezzi - valid_prices = [p.price for p in product_list if p.price > 0] - product.price = sum(valid_prices) / len(valid_prices) if valid_prices else 0.0 - - aggregated_products.append(product) + try: + # Usa aggregate_single_asset per aggregare ogni simbolo + aggregated = ProductInfo.aggregate_single_asset(product_list) + + # aggregate_single_asset calcola il volume medio, ma per multi_assets + # vogliamo il volume totale. Ricalcoliamo il volume come somma dopo il filtro USD + # Dobbiamo rifare il filtro USD per contare correttamente + currencies = set(p.currency for p in product_list if p.currency) + if len(currencies) > 1: + product_list = [p for p in product_list if p.currency.upper() == "USD"] + + # Volume totale + aggregated.volume_24h = sum(p.volume_24h for p in product_list if p.volume_24h > 0) + + aggregated_products.append(aggregated) + except ValueError: + # Se aggregate_single_asset fallisce (es. no USD when currencies differ), salta + continue + return aggregated_products @staticmethod @@ -100,6 +95,14 @@ class ProductInfo(BaseModel): if not assets_list: raise ValueError("aggregate_single_asset requires at least one ProductInfo") + # Controllo valuta: se non sono tutte uguali, filtra solo USD + currencies = set(p.currency for p in assets_list if p.currency) + if len(currencies) > 1: + # Valute diverse: filtra solo USD + assets_list = [p for p in assets_list if p.currency.upper() == "USD"] + if not assets_list: + raise ValueError("aggregate_single_asset: no USD products available when currencies differ") + # Aggregazione per ogni Exchange aggregated: ProductInfo = ProductInfo() first = assets_list[0] diff --git a/tests/utils/test_market_aggregator.py b/tests/utils/test_market_aggregator.py index 720f668..93f96fa 100644 --- a/tests/utils/test_market_aggregator.py +++ b/tests/utils/test_market_aggregator.py @@ -66,13 +66,13 @@ class TestMarketDataAggregator: assert btc_info is not None 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) # type: ignore - assert btc_info.volume_24h == pytest.approx(1050.0, rel=1e-3) # type: ignore + assert btc_info.volume_24h == pytest.approx(2100.0, rel=1e-3) # type: ignore # Total volume (1000 + 1100) assert btc_info.currency == "USD" assert eth_info is not None 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) # type: ignore - assert eth_info.volume_24h == pytest.approx(2050.0, rel=1e-3) # type: ignore + assert eth_info.volume_24h == pytest.approx(4100.0, rel=1e-3) # type: ignore # Total volume (2000 + 2100) assert eth_info.currency == "USD" def test_aggregate_product_info_with_no_data(self): @@ -141,12 +141,10 @@ class TestMarketDataAggregator: assert info is not None assert info.id == "BTC_AGGREGATED" assert info.symbol == "BTC" - assert info.currency in ["USD", "EUR"] # Can be either, depending on which is found first - # When aggregating different currencies, VWAP is calculated - # (100000.0 * 1000.0 + 70000.0 * 800.0) / (1000.0 + 800.0) - expected_price = (100000.0 * 1000.0 + 70000.0 * 800.0) / (1000.0 + 800.0) - assert info.price == pytest.approx(expected_price, rel=1e-3) # type: ignore - assert info.volume_24h == pytest.approx(900.0, rel=1e-3) # type: ignore # Average of volumes + assert info.currency == "USD" # Only USD products are kept + # When currencies differ, only USD is aggregated (only Provider1 in this case) + assert info.price == pytest.approx(100000.0, rel=1e-3) # type: ignore + assert info.volume_24h == pytest.approx(1000.0, rel=1e-3) # type: ignore # Only USD volume # ===== Tests for aggregate_single_asset =====