12 fix docs #13

Merged
Berack96 merged 18 commits from 12-fix-docs into main 2025-10-02 01:41:00 +02:00
13 changed files with 84 additions and 94 deletions
Showing only changes of commit 59a38c6e32 - Show all commits

View File

@@ -154,7 +154,7 @@ class ProviderTester:
if product:
print(f"📦 Product: {product.symbol} (ID: {product.id})")
print(f" Price: ${product.price:.2f}, Quote: {product.quote_currency}")
print(f" Status: {product.status}, Volume 24h: {product.volume_24h:,.2f}")
print(f" Volume 24h: {product.volume_24h:,.2f}")
else:
print(f"📦 Product: Nessun prodotto trovato per {symbol}")

View File

@@ -46,7 +46,6 @@ class ProductInfo(BaseModel):
symbol: str = ""
price: float = 0.0
volume_24h: float = 0.0
status: str = ""
quote_currency: str = ""
class Price(BaseModel):
@@ -59,4 +58,4 @@ class Price(BaseModel):
open: float = 0.0
close: float = 0.0
volume: float = 0.0
time: str = ""
timestamp_ms: int = 0 # Timestamp in milliseconds

View File

@@ -3,16 +3,25 @@ from datetime import datetime
from binance.client import Client
from .base import ProductInfo, BaseWrapper, Price
def get_product(currency: str, ticker_data: dict[str, str]) -> 'ProductInfo':
def get_product(currency: str, ticker_data: dict[str, str]) -> ProductInfo:
product = ProductInfo()
product.id = ticker_data.get('symbol')
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.status = "TRADING" # Binance non fornisce status esplicito
product.quote_currency = currency
return product
def get_price(kline_data: list) -> Price:
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]
return price
class BinanceWrapper(BaseWrapper):
"""
Wrapper per le API autenticate di Binance.\n
@@ -63,15 +72,5 @@ class BinanceWrapper(BaseWrapper):
interval=Client.KLINE_INTERVAL_1HOUR,
limit=limit,
)
return [get_price(kline) for kline in klines]
prices = []
for kline in klines:
price = Price()
price.open = float(kline[1])
price.high = float(kline[2])
price.low = float(kline[3])
price.close = float(kline[4])
price.volume = float(kline[5])
price.time = str(datetime.fromtimestamp(kline[0] / 1000))
prices.append(price)
return prices

View File

@@ -6,24 +6,22 @@ from coinbase.rest.types.product_types import Candle, GetProductResponse, Produc
from .base import ProductInfo, BaseWrapper, Price
def get_product(product_data: GetProductResponse | Product) -> 'ProductInfo':
def get_product(product_data: GetProductResponse | Product) -> ProductInfo:
product = ProductInfo()
product.id = product_data.product_id or ""
product.symbol = product_data.base_currency_id or ""
product.price = float(product_data.price) if product_data.price else 0.0
product.volume_24h = float(product_data.volume_24h) if product_data.volume_24h else 0.0
# TODO Check what status means in Coinbase
product.status = product_data.status or ""
return product
def get_price(candle_data: Candle) -> 'Price':
def get_price(candle_data: Candle) -> Price:
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.time = str(candle_data.start) if candle_data.start else ""
price.timestamp_ms = int(candle_data.start) * 1000 if candle_data.start else 0
return price

View File

@@ -1,26 +1,26 @@
import os
import requests
from typing import Optional, Dict, Any
from .base import ProductInfo, BaseWrapper, Price
def get_product(asset_data: dict) -> 'ProductInfo':
def get_product(asset_data: dict) -> ProductInfo:
product = ProductInfo()
product.id = asset_data['FROMSYMBOL'] + '-' + asset_data['TOSYMBOL']
product.symbol = asset_data['FROMSYMBOL']
product.price = float(asset_data['PRICE'])
product.volume_24h = float(asset_data['VOLUME24HOUR'])
product.status = "" # Cryptocompare does not provide status
product.id = asset_data.get('FROMSYMBOL', '') + '-' + asset_data.get('TOSYMBOL', '')
product.symbol = asset_data.get('FROMSYMBOL', '')
product.price = float(asset_data.get('PRICE', 0))
product.volume_24h = float(asset_data.get('VOLUME24HOUR', 0))
assert product.price > 0, "Invalid price data received from CryptoCompare"
return product
def get_price(price_data: dict) -> 'Price':
def get_price(price_data: dict) -> Price:
price = Price()
price.high = float(price_data['high'])
price.low = float(price_data['low'])
price.open = float(price_data['open'])
price.close = float(price_data['close'])
price.volume = float(price_data['volumeto'])
price.time = str(price_data['time'])
price.high = float(price_data.get('high', 0))
price.low = float(price_data.get('low', 0))
price.open = float(price_data.get('open', 0))
copilot-pull-request-reviewer[bot] commented 2025-10-02 01:29:52 +02:00 (Migrated from github.com)
Review

Using assertions for API validation is not recommended in production code. Consider raising a more specific exception like ValueError or APIDataError instead.

    if product.price <= 0:
        raise ValueError("Invalid price data received from CryptoCompare")
Using assertions for API validation is not recommended in production code. Consider raising a more specific exception like `ValueError` or `APIDataError` instead. ```suggestion if product.price <= 0: raise ValueError("Invalid price data received from CryptoCompare") ```
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"
return price
@@ -39,7 +39,7 @@ class CryptoCompareWrapper(BaseWrapper):
self.api_key = api_key
self.currency = currency
def __request(self, endpoint: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
def __request(self, endpoint: str, params: dict[str, str] | None = None) -> dict[str, str]:
if params is None:
params = {}
params['api_key'] = self.api_key
@@ -67,7 +67,7 @@ class CryptoCompareWrapper(BaseWrapper):
assets.append(get_product(asset_data))
return assets
def get_historical_prices(self, asset_id: str, limit: int = 100) -> list[dict]:
def get_historical_prices(self, asset_id: str, limit: int = 100) -> list[Price]:
response = self.__request("/data/v2/histohour", params = {
"fsym": asset_id,
"tsym": self.currency,

View File

@@ -12,7 +12,6 @@ def create_product_info(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.status = "trading" if product.price > 0 else "offline"
product.quote_currency = product.id.split('-')[0] # La valuta è la parte dopo il '-'
return product
@@ -26,7 +25,7 @@ def create_price_from_history(hist_data: dict[str, str]) -> Price:
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.time = hist_data.get('Timestamp', '')
price.timestamp_ms = int(hist_data.get('Timestamp', '0'))
return price

View File

@@ -2,7 +2,7 @@ import statistics
from app.markets.base import ProductInfo, Price
def aggregate_history_prices(prices: dict[str, list[Price]]) -> list[float]:
def aggregate_history_prices(prices: dict[str, list[Price]]) -> list[Price]:
"""Aggrega i prezzi storici per symbol calcolando la media"""
raise NotImplementedError("Funzione non ancora implementata per problemi di timestamp he deve essere uniformato prima di usare questa funzione.")
# TODO implementare l'aggregazione dopo aver modificato la classe Price in modo che abbia un timestamp integer
@@ -40,11 +40,6 @@ def aggregate_product_info(products: dict[str, list[ProductInfo]]) -> list[Produ
product.symbol = symbol
product.quote_currency = next(p.quote_currency for p in product_list if p.quote_currency)
statuses = {}
for p in product_list:
statuses[p.status] = statuses.get(p.status, 0) + 1
product.status = max(statuses, key=statuses.get) if statuses else ""
prices = [p.price for p in product_list]
product.price = statistics.mean(prices)

View File

@@ -45,8 +45,9 @@ class TestBinance:
assert isinstance(history, list)
assert len(history) == 5
for entry in history:
assert hasattr(entry, 'time')
assert hasattr(entry, 'timestamp_ms')
assert hasattr(entry, 'close')
assert hasattr(entry, 'high')
assert entry.close > 0
assert entry.high > 0
assert entry.timestamp_ms > 0

View File

@@ -47,8 +47,9 @@ class TestCoinBase:
assert isinstance(history, list)
assert len(history) == 5
for entry in history:
assert hasattr(entry, 'time')
assert hasattr(entry, 'timestamp_ms')
assert hasattr(entry, 'close')
assert hasattr(entry, 'high')
assert entry.close > 0
assert entry.high > 0
assert entry.timestamp_ms > 0

View File

@@ -49,8 +49,9 @@ class TestCryptoCompare:
assert isinstance(history, list)
assert len(history) == 5
for entry in history:
assert hasattr(entry, 'time')
assert hasattr(entry, 'timestamp_ms')
assert hasattr(entry, 'close')
assert hasattr(entry, 'high')
assert entry.close > 0
assert entry.high > 0
assert entry.timestamp_ms > 0

View File

@@ -5,7 +5,7 @@ from app.social.reddit import MAX_COMMENTS, RedditWrapper
@pytest.mark.social
@pytest.mark.api
@pytest.mark.skipif(not(os.getenv("REDDIT_CLIENT_ID")) or not(os.getenv("REDDIT_API_CLIENT_ID")) or not os.getenv("REDDIT_API_CLIENT_SECRET"), reason="REDDIT_CLIENT_ID and REDDIT_API_CLIENT_SECRET not set in environment variables")
@pytest.mark.skipif(not(os.getenv("REDDIT_API_CLIENT_ID")) or not os.getenv("REDDIT_API_CLIENT_SECRET"), reason="REDDIT_CLIENT_ID and REDDIT_API_CLIENT_SECRET not set in environment variables")
class TestRedditWrapper:
def test_initialization(self):
wrapper = RedditWrapper()

View File

@@ -43,13 +43,14 @@ class TestYFinance:
def test_yfinance_crypto_history(self):
market = YFinanceWrapper()
history = market.get_historical_prices("BTC", limit=3)
history = market.get_historical_prices("BTC", limit=5)
assert history is not None
assert isinstance(history, list)
assert len(history) == 3
assert len(history) == 5
for entry in history:
assert hasattr(entry, 'time')
assert hasattr(entry, 'timestamp_ms')
assert hasattr(entry, 'close')
assert hasattr(entry, 'high')
assert entry.close > 0
assert hasattr(entry, 'open')
assert entry.open > 0
assert entry.high > 0
assert entry.timestamp_ms > 0

View File

@@ -7,21 +7,30 @@ from app.utils.market_aggregation import aggregate_history_prices, aggregate_pro
@pytest.mark.market
class TestMarketDataAggregator:
def __product(self, symbol: str, price: float, volume: float, status: str, currency: str) -> ProductInfo:
def __product(self, symbol: str, price: float, volume: float, currency: str) -> ProductInfo:
prod = ProductInfo()
prod.id=f"{symbol}-{currency}"
prod.symbol=symbol
prod.price=price
prod.volume_24h=volume
prod.status=status
prod.quote_currency=currency
return prod
def __price(self, timestamp_ms: int, high: float, low: float, open: float, close: float, volume: float) -> Price:
price = Price()
price.timestamp_ms = timestamp_ms
price.high = high
price.low = low
price.open = open
price.close = close
price.volume = volume
return price
def test_aggregate_product_info(self):
products: dict[str, list[ProductInfo]] = {
"Provider1": [self.__product("BTC", 50000.0, 1000.0, "active", "USD")],
"Provider2": [self.__product("BTC", 50100.0, 1100.0, "active", "USD")],
"Provider3": [self.__product("BTC", 49900.0, 900.0, "inactive", "USD")],
"Provider1": [self.__product("BTC", 50000.0, 1000.0, "USD")],
"Provider2": [self.__product("BTC", 50100.0, 1100.0, "USD")],
"Provider3": [self.__product("BTC", 49900.0, 900.0, "USD")],
}
aggregated = aggregate_product_info(products)
@@ -35,18 +44,17 @@ class TestMarketDataAggregator:
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)
assert info.status == "active"
assert info.quote_currency == "USD"
def test_aggregate_product_info_multiple_symbols(self):
products = {
"Provider1": [
self.__product("BTC", 50000.0, 1000.0, "active", "USD"),
self.__product("ETH", 4000.0, 2000.0, "active", "USD"),
self.__product("BTC", 50000.0, 1000.0, "USD"),
self.__product("ETH", 4000.0, 2000.0, "USD"),
],
"Provider2": [
self.__product("BTC", 50100.0, 1100.0, "active", "USD"),
self.__product("ETH", 4050.0, 2100.0, "active", "USD"),
self.__product("BTC", 50100.0, 1100.0, "USD"),
self.__product("ETH", 4050.0, 2100.0, "USD"),
],
}
@@ -60,45 +68,33 @@ class TestMarketDataAggregator:
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)
assert btc_info.status == "active"
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)
assert eth_info.status == "active"
assert eth_info.quote_currency == "USD"
def test_aggregate_history_prices(self):
"""Test aggregazione di prezzi storici usando aggregate_history_prices"""
price1 = Price(
timestamp="2024-06-01T00:00:00Z",
price=50000.0,
source="exchange1"
)
price2 = Price(
timestamp="2024-06-01T00:00:00Z",
price=50100.0,
source="exchange2"
)
price3 = Price(
timestamp="2024-06-01T01:00:00Z",
price=50200.0,
source="exchange1"
)
price4 = Price(
timestamp="2024-06-01T01:00:00Z",
price=50300.0,
source="exchange2"
)
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),
],
"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),
],
}
prices = [price1, price2, price3, price4]
aggregated_prices = aggregate_history_prices(prices)
assert len(aggregated_prices) == 2
assert aggregated_prices[0].timestamp == "2024-06-01T00:00:00Z"
assert aggregated_prices[0].price == pytest.approx(50050.0, rel=1e-3)
assert aggregated_prices[1].timestamp == "2024-06-01T01:00:00Z"
assert aggregated_prices[1].price == pytest.approx(50250.0, rel=1e-3)
aggregated = aggregate_history_prices(prices)
assert len(aggregated) == 2
assert aggregated[0].timestamp_ms == 1685577600000
assert aggregated[0].high == pytest.approx(50050.0, rel=1e-3)
assert aggregated[0].low == pytest.approx(49500.0, rel=1e-3)
assert aggregated[1].timestamp_ms == 1685581200000
assert aggregated[1].high == pytest.approx(50250.0, rel=1e-3)
assert aggregated[1].low == pytest.approx(49800.0, rel=1e-3)