Refactor Predictor and market data handling
- Added Predictor class with input preparation and instructions for financial strategy generation. - Removed PredictorAgent class and integrated its functionality into the new Predictor module. - Created a base market API wrapper and specific implementations for Coinbase and CryptoCompare. - Introduced PublicBinanceAgent for fetching public prices from Binance. - Refactored ToolAgent to utilize the new Predictor and market API wrappers for improved data handling and predictions. - Updated models to streamline the selection of available LLM providers. - Removed deprecated signer classes for Coinbase and CryptoCompare.
This commit is contained in:
29
src/app/markets/__init__.py
Normal file
29
src/app/markets/__init__.py
Normal file
@@ -0,0 +1,29 @@
|
||||
import os
|
||||
from app.markets.base import BaseWrapper
|
||||
from app.markets.coinbase import CoinBaseWrapper
|
||||
from app.markets.cryptocompare import CryptoCompareWrapper
|
||||
|
||||
# TODO Dare la priorità in base alla qualità del servizio
|
||||
# TODO Aggiungere altri wrapper se necessario
|
||||
def get_first_available_market_api(currency:str = "USD") -> BaseWrapper:
|
||||
"""
|
||||
Restituisce il primo wrapper disponibile in base alle configurazioni del file .env e alle chiavi API presenti.
|
||||
La priorità è data a Coinbase, poi a CryptoCompare.
|
||||
Se non sono presenti chiavi API, restituisce una eccezione.
|
||||
:param currency: Valuta di riferimento (default "USD")
|
||||
:return: Lista di istanze di wrapper
|
||||
"""
|
||||
wrappers = []
|
||||
|
||||
api_key = os.getenv("COINBASE_API_KEY")
|
||||
api_secret = os.getenv("COINBASE_API_SECRET")
|
||||
if api_key and api_secret:
|
||||
wrappers.append(CoinBaseWrapper(api_key=api_key, api_private_key=api_secret, currency=currency))
|
||||
|
||||
api_key = os.getenv("CRYPTOCOMPARE_API_KEY")
|
||||
if api_key:
|
||||
wrappers.append(CryptoCompareWrapper(api_key=api_key, currency=currency))
|
||||
|
||||
assert wrappers, "No valid API keys set in environment variables."
|
||||
return wrappers[0]
|
||||
|
||||
67
src/app/markets/base.py
Normal file
67
src/app/markets/base.py
Normal file
@@ -0,0 +1,67 @@
|
||||
from coinbase.rest.types.product_types import Candle, GetProductResponse
|
||||
|
||||
|
||||
class BaseWrapper:
|
||||
def get_product(self, asset_id: str) -> 'ProductInfo':
|
||||
raise NotImplementedError
|
||||
def get_products(self, asset_ids: list[str]) -> list['ProductInfo']:
|
||||
raise NotImplementedError
|
||||
def get_all_products(self) -> list['ProductInfo']:
|
||||
raise NotImplementedError
|
||||
def get_historical_prices(self, asset_id: str = "BTC") -> list['Price']:
|
||||
raise NotImplementedError
|
||||
|
||||
class ProductInfo:
|
||||
id: str
|
||||
symbol: str
|
||||
price: float
|
||||
volume_24h: float
|
||||
status: str
|
||||
quote_currency: str
|
||||
|
||||
def from_coinbase(product_data: GetProductResponse) -> 'ProductInfo':
|
||||
product = ProductInfo()
|
||||
product.id = product_data.product_id
|
||||
product.symbol = product_data.base_currency_id
|
||||
product.price = float(product_data.price)
|
||||
product.volume_24h = float(product_data.volume_24h) if product_data.volume_24h else 0
|
||||
# TODO Check what status means in Coinbase
|
||||
product.status = product_data.status
|
||||
return product
|
||||
|
||||
def from_cryptocompare(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
|
||||
return product
|
||||
|
||||
class Price:
|
||||
high: float
|
||||
low: float
|
||||
open: float
|
||||
close: float
|
||||
volume: float
|
||||
time: str
|
||||
|
||||
def from_coinbase(candle_data: Candle) -> 'Price':
|
||||
price = Price()
|
||||
price.high = float(candle_data.high)
|
||||
price.low = float(candle_data.low)
|
||||
price.open = float(candle_data.open)
|
||||
price.close = float(candle_data.close)
|
||||
price.volume = float(candle_data.volume)
|
||||
price.time = str(candle_data.start)
|
||||
return price
|
||||
|
||||
def from_cryptocompare(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'])
|
||||
return price
|
||||
27
src/app/markets/binance.py
Normal file
27
src/app/markets/binance.py
Normal file
@@ -0,0 +1,27 @@
|
||||
# Versione pubblica senza autenticazione
|
||||
from binance.client import Client
|
||||
|
||||
class PublicBinanceAgent:
|
||||
def __init__(self):
|
||||
# Client pubblico (senza credenziali)
|
||||
self.client = Client()
|
||||
|
||||
def get_public_prices(self):
|
||||
"""Ottiene prezzi pubblici"""
|
||||
try:
|
||||
btc_price = self.client.get_symbol_ticker(symbol="BTCUSDT")
|
||||
eth_price = self.client.get_symbol_ticker(symbol="ETHUSDT")
|
||||
|
||||
return {
|
||||
'BTC_USD': float(btc_price['price']),
|
||||
'ETH_USD': float(eth_price['price']),
|
||||
'source': 'binance_public'
|
||||
}
|
||||
except Exception as e:
|
||||
print(f"Errore: {e}")
|
||||
return None
|
||||
|
||||
# Uso senza credenziali
|
||||
public_agent = PublicBinanceAgent()
|
||||
public_prices = public_agent.get_public_prices()
|
||||
print(public_prices)
|
||||
35
src/app/markets/coinbase.py
Normal file
35
src/app/markets/coinbase.py
Normal file
@@ -0,0 +1,35 @@
|
||||
from coinbase.rest import RESTClient
|
||||
from app.markets.base import ProductInfo, BaseWrapper, Price
|
||||
|
||||
class CoinBaseWrapper(BaseWrapper):
|
||||
def __init__(self, api_key:str, api_private_key:str, currency: str = "USD"):
|
||||
assert api_key is not None, "API key is required"
|
||||
assert api_private_key is not None, "API private key is required"
|
||||
|
||||
self.currency = currency
|
||||
self.client: RESTClient = RESTClient(
|
||||
api_key=api_key,
|
||||
api_secret=api_private_key
|
||||
)
|
||||
|
||||
def __format(self, asset_id: str) -> str:
|
||||
return asset_id if '-' in asset_id else f"{asset_id}-{self.currency}"
|
||||
|
||||
def get_product(self, asset_id: str) -> ProductInfo:
|
||||
asset_id = self.__format(asset_id)
|
||||
asset = self.client.get_product(asset_id)
|
||||
return ProductInfo.from_coinbase(asset)
|
||||
|
||||
def get_products(self, asset_ids: list[str]) -> list[ProductInfo]:
|
||||
all_asset_ids = [self.__format(asset_id) for asset_id in asset_ids]
|
||||
assets = self.client.get_products(all_asset_ids)
|
||||
return [ProductInfo.from_coinbase(asset) for asset in assets.products]
|
||||
|
||||
def get_all_products(self) -> list[ProductInfo]:
|
||||
assets = self.client.get_products()
|
||||
return [ProductInfo.from_coinbase(asset) for asset in assets.products]
|
||||
|
||||
def get_historical_prices(self, asset_id: str = "BTC") -> list[Price]:
|
||||
asset_id = self.__format(asset_id)
|
||||
data = self.client.get_candles(product_id=asset_id)
|
||||
return [Price.from_coinbase(candle) for candle in data.candles]
|
||||
54
src/app/markets/cryptocompare.py
Normal file
54
src/app/markets/cryptocompare.py
Normal file
@@ -0,0 +1,54 @@
|
||||
import requests
|
||||
from app.markets.base import ProductInfo, BaseWrapper, Price
|
||||
|
||||
BASE_URL = "https://min-api.cryptocompare.com"
|
||||
|
||||
class CryptoCompareWrapper(BaseWrapper):
|
||||
def __init__(self, api_key:str, currency:str='USD'):
|
||||
assert api_key is not None, "API key is required"
|
||||
|
||||
self.api_key = api_key
|
||||
self.currency = currency
|
||||
|
||||
def __request(self, endpoint: str, params: dict = None) -> dict:
|
||||
if params is None:
|
||||
params = {}
|
||||
params['api_key'] = self.api_key
|
||||
|
||||
response = requests.get(f"{BASE_URL}{endpoint}", params=params)
|
||||
return response.json()
|
||||
|
||||
def get_product(self, asset_id: str) -> ProductInfo:
|
||||
response = self.__request("/data/pricemultifull", params = {
|
||||
"fsyms": asset_id,
|
||||
"tsyms": self.currency
|
||||
})
|
||||
data = response.get('RAW', {}).get(asset_id, {}).get(self.currency, {})
|
||||
return ProductInfo.from_cryptocompare(data)
|
||||
|
||||
def get_products(self, asset_ids: list[str]) -> list[ProductInfo]:
|
||||
response = self.__request("/data/pricemultifull", params = {
|
||||
"fsyms": ",".join(asset_ids),
|
||||
"tsyms": self.currency
|
||||
})
|
||||
assets = []
|
||||
data = response.get('RAW', {})
|
||||
for asset_id in asset_ids:
|
||||
asset_data = data.get(asset_id, {}).get(self.currency, {})
|
||||
assets.append(ProductInfo.from_cryptocompare(asset_data))
|
||||
return assets
|
||||
|
||||
def get_all_products(self) -> list[ProductInfo]:
|
||||
raise NotImplementedError("CryptoCompare does not support fetching all assets")
|
||||
|
||||
def get_historical_prices(self, asset_id: str, day_back: int = 10) -> list[dict]:
|
||||
assert day_back <= 30, "day_back should be less than or equal to 30"
|
||||
response = self.__request("/data/v2/histohour", params = {
|
||||
"fsym": asset_id,
|
||||
"tsym": self.currency,
|
||||
"limit": day_back * 24
|
||||
})
|
||||
|
||||
data = response.get('Data', {}).get('Data', [])
|
||||
prices = [Price.from_cryptocompare(price_data) for price_data in data]
|
||||
return prices
|
||||
Reference in New Issue
Block a user