Rinominato 'quote_currency' in 'currency' e aggiornato il trattamento del timestamp in Price
This commit is contained in:
@@ -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:
|
||||
"""
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 != ''
|
||||
|
||||
@@ -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 != ''
|
||||
|
||||
@@ -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 != ''
|
||||
|
||||
@@ -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 != ''
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user