From f5816bb74fdaae588f5a066832eba7a9db71c2f4 Mon Sep 17 00:00:00 2001 From: Berack96 Date: Sun, 5 Oct 2025 19:06:39 +0200 Subject: [PATCH] Rinominato 'quote_currency' in 'currency' e aggiornato il trattamento del timestamp in Price --- src/app/base/markets.py | 22 ++++++++++++++-- src/app/markets/binance.py | 6 +++-- src/app/markets/coinbase.py | 4 ++- src/app/markets/cryptocompare.py | 5 ++-- src/app/markets/yfinance.py | 6 +++-- src/app/utils/market_aggregation.py | 17 ++++++------- tests/agents/test_predictor.py | 4 --- tests/api/test_binance.py | 4 +-- tests/api/test_coinbase.py | 4 +-- tests/api/test_cryptocompare.py | 4 +-- tests/api/test_yfinance.py | 4 +-- tests/utils/test_market_aggregator.py | 36 +++++++++++++++++---------- 12 files changed, 73 insertions(+), 43 deletions(-) diff --git a/src/app/base/markets.py b/src/app/base/markets.py index acb0c9c..052d033 100644 --- a/src/app/base/markets.py +++ b/src/app/base/markets.py @@ -1,3 +1,4 @@ +from datetime import datetime from pydantic import BaseModel @@ -10,7 +11,7 @@ class ProductInfo(BaseModel): symbol: str = "" price: float = 0.0 volume_24h: float = 0.0 - quote_currency: str = "" + currency: str = "" class Price(BaseModel): """ @@ -22,7 +23,24 @@ class Price(BaseModel): open: float = 0.0 close: float = 0.0 volume: float = 0.0 - timestamp_ms: int = 0 # Timestamp in milliseconds + timestamp: str = "" + """Timestamp con formato YYYY-MM-DD HH:MM""" + + def set_timestamp(self, timestamp_ms: int | None = None, timestamp_s: int | None = None) -> None: + """ + Imposta il timestamp in millisecondi. + Args: + timestamp (int | datetime): Il timestamp in millisecondi o come oggetto datetime. + """ + if timestamp_ms is not None: + timestamp = timestamp_ms // 1000 + elif timestamp_s is not None: + timestamp = timestamp_s + else: + raise ValueError("Either timestamp_ms or timestamp_s must be provided") + assert timestamp > 0, "Invalid timestamp data received" + + self.timestamp = datetime.fromtimestamp(timestamp).strftime('%Y-%m-%d %H:%M') class MarketWrapper: """ diff --git a/src/app/markets/binance.py b/src/app/markets/binance.py index c7951ef..c277e46 100644 --- a/src/app/markets/binance.py +++ b/src/app/markets/binance.py @@ -10,17 +10,19 @@ def extract_product(currency: str, ticker_data: dict[str, Any]) -> ProductInfo: product.symbol = ticker_data.get('symbol', '').replace(currency, '') product.price = float(ticker_data.get('price', 0)) product.volume_24h = float(ticker_data.get('volume', 0)) - product.quote_currency = currency + product.currency = currency return product def extract_price(kline_data: list[Any]) -> Price: + timestamp = kline_data[0] + price = Price() price.open = float(kline_data[1]) price.high = float(kline_data[2]) price.low = float(kline_data[3]) price.close = float(kline_data[4]) price.volume = float(kline_data[5]) - price.timestamp_ms = kline_data[0] + price.set_timestamp(timestamp_ms=timestamp) return price class BinanceWrapper(MarketWrapper): diff --git a/src/app/markets/coinbase.py b/src/app/markets/coinbase.py index d525d28..c59382b 100644 --- a/src/app/markets/coinbase.py +++ b/src/app/markets/coinbase.py @@ -15,13 +15,15 @@ def extract_product(product_data: GetProductResponse | Product) -> ProductInfo: return product def extract_price(candle_data: Candle) -> Price: + timestamp = int(candle_data.start) if candle_data.start else 0 + price = Price() price.high = float(candle_data.high) if candle_data.high else 0.0 price.low = float(candle_data.low) if candle_data.low else 0.0 price.open = float(candle_data.open) if candle_data.open else 0.0 price.close = float(candle_data.close) if candle_data.close else 0.0 price.volume = float(candle_data.volume) if candle_data.volume else 0.0 - price.timestamp_ms = int(candle_data.start) * 1000 if candle_data.start else 0 + price.set_timestamp(timestamp_s=timestamp) return price diff --git a/src/app/markets/cryptocompare.py b/src/app/markets/cryptocompare.py index e3f67d6..5431267 100644 --- a/src/app/markets/cryptocompare.py +++ b/src/app/markets/cryptocompare.py @@ -14,14 +14,15 @@ def extract_product(asset_data: dict[str, Any]) -> ProductInfo: return product def extract_price(price_data: dict[str, Any]) -> Price: + timestamp = price_data.get('time', 0) + price = Price() price.high = float(price_data.get('high', 0)) price.low = float(price_data.get('low', 0)) price.open = float(price_data.get('open', 0)) price.close = float(price_data.get('close', 0)) price.volume = float(price_data.get('volumeto', 0)) - price.timestamp_ms = price_data.get('time', 0) * 1000 - assert price.timestamp_ms > 0, "Invalid timestamp data received from CryptoCompare" + price.set_timestamp(timestamp_s=timestamp) return price diff --git a/src/app/markets/yfinance.py b/src/app/markets/yfinance.py index a454ad6..2670eda 100644 --- a/src/app/markets/yfinance.py +++ b/src/app/markets/yfinance.py @@ -12,20 +12,22 @@ def extract_product(stock_data: dict[str, str]) -> ProductInfo: 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.quote_currency = product.id.split('-')[1] # La valuta è la parte dopo il '-' + product.currency = product.id.split('-')[1] # La valuta è la parte dopo il '-' return product def extract_price(hist_data: dict[str, str]) -> Price: """ Converte i dati storici di YFinanceTools in Price. """ + timestamp = int(hist_data.get('Timestamp', '0')) + price = Price() 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.timestamp_ms = int(hist_data.get('Timestamp', '0')) + price.set_timestamp(timestamp_ms=timestamp) return price diff --git a/src/app/utils/market_aggregation.py b/src/app/utils/market_aggregation.py index 0fb56f0..7f9f32c 100644 --- a/src/app/utils/market_aggregation.py +++ b/src/app/utils/market_aggregation.py @@ -4,25 +4,24 @@ from app.base.markets import ProductInfo, Price def aggregate_history_prices(prices: dict[str, list[Price]]) -> list[Price]: """ - Aggrega i prezzi storici per symbol calcolando la media oraria. + Aggrega i prezzi storici per symbol calcolando la media. Args: prices (dict[str, list[Price]]): Mappa provider -> lista di Price Returns: - list[Price]: Lista di Price aggregati per ora + list[Price]: Lista di Price aggregati per timestamp """ - # Costruiamo una mappa timestamp_h -> lista di Price - timestamped_prices: dict[int, list[Price]] = {} + # Costruiamo una mappa timestamp -> lista di Price + timestamped_prices: dict[str, list[Price]] = {} for _, price_list in prices.items(): for price in price_list: - time = price.timestamp_ms - (price.timestamp_ms % 3600000) # arrotonda all'ora (non dovrebbe essere necessario) - timestamped_prices.setdefault(time, []).append(price) + timestamped_prices.setdefault(price.timestamp, []).append(price) - # Ora aggregiamo i prezzi per ogni ora + # Ora aggregiamo i prezzi per ogni timestamp aggregated_prices: list[Price] = [] for time, price_list in timestamped_prices.items(): price = Price() - price.timestamp_ms = time + price.timestamp = time price.high = statistics.mean([p.high for p in price_list]) price.low = statistics.mean([p.low for p in price_list]) price.open = statistics.mean([p.open for p in price_list]) @@ -53,7 +52,7 @@ def aggregate_product_info(products: dict[str, list[ProductInfo]]) -> list[Produ product.id = f"{symbol}_AGGREGATED" product.symbol = symbol - product.quote_currency = next(p.quote_currency for p in product_list if p.quote_currency) + product.currency = next(p.currency for p in product_list if p.currency) 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 diff --git a/tests/agents/test_predictor.py b/tests/agents/test_predictor.py index 802c97f..9a2ac11 100644 --- a/tests/agents/test_predictor.py +++ b/tests/agents/test_predictor.py @@ -41,10 +41,6 @@ class TestPredictor: inputs = self.inputs() unified_checks(AppModels.GEMINI, inputs) - def test_ollama_qwen_1b_model_output(self): - inputs = self.inputs() - unified_checks(AppModels.OLLAMA_QWEN_1B, inputs) - def test_ollama_qwen_4b_model_output(self): inputs = self.inputs() unified_checks(AppModels.OLLAMA_QWEN_4B, inputs) diff --git a/tests/api/test_binance.py b/tests/api/test_binance.py index dc4bfcb..b4ea0bb 100644 --- a/tests/api/test_binance.py +++ b/tests/api/test_binance.py @@ -45,9 +45,9 @@ class TestBinance: assert isinstance(history, list) assert len(history) == 5 for entry in history: - assert hasattr(entry, 'timestamp_ms') + assert hasattr(entry, 'timestamp') assert hasattr(entry, 'close') assert hasattr(entry, 'high') assert entry.close > 0 assert entry.high > 0 - assert entry.timestamp_ms > 0 + assert entry.timestamp != '' diff --git a/tests/api/test_coinbase.py b/tests/api/test_coinbase.py index 3ab8d43..e114f4c 100644 --- a/tests/api/test_coinbase.py +++ b/tests/api/test_coinbase.py @@ -47,9 +47,9 @@ class TestCoinBase: assert isinstance(history, list) assert len(history) == 5 for entry in history: - assert hasattr(entry, 'timestamp_ms') + assert hasattr(entry, 'timestamp') assert hasattr(entry, 'close') assert hasattr(entry, 'high') assert entry.close > 0 assert entry.high > 0 - assert entry.timestamp_ms > 0 + assert entry.timestamp != '' diff --git a/tests/api/test_cryptocompare.py b/tests/api/test_cryptocompare.py index 3c9133a..23deaf3 100644 --- a/tests/api/test_cryptocompare.py +++ b/tests/api/test_cryptocompare.py @@ -49,9 +49,9 @@ class TestCryptoCompare: assert isinstance(history, list) assert len(history) == 5 for entry in history: - assert hasattr(entry, 'timestamp_ms') + assert hasattr(entry, 'timestamp') assert hasattr(entry, 'close') assert hasattr(entry, 'high') assert entry.close > 0 assert entry.high > 0 - assert entry.timestamp_ms > 0 + assert entry.timestamp != '' diff --git a/tests/api/test_yfinance.py b/tests/api/test_yfinance.py index 4971ccd..fa4174a 100644 --- a/tests/api/test_yfinance.py +++ b/tests/api/test_yfinance.py @@ -48,9 +48,9 @@ class TestYFinance: assert isinstance(history, list) assert len(history) == 5 for entry in history: - assert hasattr(entry, 'timestamp_ms') + assert hasattr(entry, 'timestamp') assert hasattr(entry, 'close') assert hasattr(entry, 'high') assert entry.close > 0 assert entry.high > 0 - assert entry.timestamp_ms > 0 + assert entry.timestamp != '' diff --git a/tests/utils/test_market_aggregator.py b/tests/utils/test_market_aggregator.py index 02c0cd2..35e3084 100644 --- a/tests/utils/test_market_aggregator.py +++ b/tests/utils/test_market_aggregator.py @@ -1,4 +1,5 @@ import pytest +from datetime import datetime from app.base.markets import ProductInfo, Price from app.utils.market_aggregation import aggregate_history_prices, aggregate_product_info @@ -13,12 +14,12 @@ class TestMarketDataAggregator: prod.symbol=symbol prod.price=price prod.volume_24h=volume - prod.quote_currency=currency + prod.currency=currency return prod - def __price(self, timestamp_ms: int, high: float, low: float, open: float, close: float, volume: float) -> Price: + def __price(self, timestamp_s: int, high: float, low: float, open: float, close: float, volume: float) -> Price: price = Price() - price.timestamp_ms = timestamp_ms + price.set_timestamp(timestamp_s=timestamp_s) price.high = high price.low = low price.open = open @@ -43,7 +44,7 @@ class TestMarketDataAggregator: 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) # type: ignore assert info.volume_24h == pytest.approx(1000.0, rel=1e-3) # type: ignore - assert info.quote_currency == "USD" + assert info.currency == "USD" def test_aggregate_product_info_multiple_symbols(self): products = { @@ -67,13 +68,13 @@ class TestMarketDataAggregator: 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.quote_currency == "USD" + 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.quote_currency == "USD" + assert eth_info.currency == "USD" def test_aggregate_product_info_with_no_data(self): products: dict[str, list[ProductInfo]] = { @@ -94,27 +95,36 @@ class TestMarketDataAggregator: assert info.symbol == "BTC" assert info.price == pytest.approx(50000.0, rel=1e-3) # type: ignore assert info.volume_24h == pytest.approx(1000.0, rel=1e-3) # type: ignore - assert info.quote_currency == "USD" + assert info.currency == "USD" def test_aggregate_history_prices(self): """Test aggregazione di prezzi storici usando aggregate_history_prices""" + timestamp_now = datetime.now() + timestamp_1h_ago = int(timestamp_now.replace(hour=timestamp_now.hour - 1).timestamp()) + timestamp_2h_ago = int(timestamp_now.replace(hour=timestamp_now.hour - 2).timestamp()) prices = { "Provider1": [ - self.__price(1685577600000, 50000.0, 49500.0, 49600.0, 49900.0, 150.0), - self.__price(1685581200000, 50200.0, 49800.0, 50000.0, 50100.0, 200.0), + self.__price(timestamp_1h_ago, 50000.0, 49500.0, 49600.0, 49900.0, 150.0), + self.__price(timestamp_2h_ago, 50200.0, 49800.0, 50000.0, 50100.0, 200.0), ], "Provider2": [ - self.__price(1685577600000, 50100.0, 49600.0, 49700.0, 50000.0, 180.0), - self.__price(1685581200000, 50300.0, 49900.0, 50100.0, 50200.0, 220.0), + self.__price(timestamp_1h_ago, 50100.0, 49600.0, 49700.0, 50000.0, 180.0), + self.__price(timestamp_2h_ago, 50300.0, 49900.0, 50100.0, 50200.0, 220.0), ], } + price = Price() + price.set_timestamp(timestamp_s=timestamp_1h_ago) + timestamp_1h_ago = price.timestamp + price.set_timestamp(timestamp_s=timestamp_2h_ago) + timestamp_2h_ago = price.timestamp + aggregated = aggregate_history_prices(prices) assert len(aggregated) == 2 - assert aggregated[0].timestamp_ms == 1685577600000 + assert aggregated[0].timestamp == timestamp_1h_ago assert aggregated[0].high == pytest.approx(50050.0, rel=1e-3) # type: ignore assert aggregated[0].low == pytest.approx(49550.0, rel=1e-3) # type: ignore - assert aggregated[1].timestamp_ms == 1685581200000 + assert aggregated[1].timestamp == timestamp_2h_ago assert aggregated[1].high == pytest.approx(50250.0, rel=1e-3) # type: ignore assert aggregated[1].low == pytest.approx(49850.0, rel=1e-3) # type: ignore