Spaces:
Sleeping
Sleeping
Add tools folder with market, sentiment, historical and analytical tools
Browse files- tools/__pycache__/analytics_tool.cpython-313.pyc +0 -0
- tools/__pycache__/historical_data_tool.cpython-313.pyc +0 -0
- tools/__pycache__/market_data.cpython-313.pyc +0 -0
- tools/__pycache__/sentiment_tool.cpython-313.pyc +0 -0
- tools/analytics_tool.py +54 -0
- tools/historical_data_tool.py +60 -0
- tools/market_data.py +31 -0
- tools/sentiment_tool.py +65 -0
tools/__pycache__/analytics_tool.cpython-313.pyc
ADDED
|
Binary file (3.01 kB). View file
|
|
|
tools/__pycache__/historical_data_tool.cpython-313.pyc
ADDED
|
Binary file (3.06 kB). View file
|
|
|
tools/__pycache__/market_data.cpython-313.pyc
ADDED
|
Binary file (1.88 kB). View file
|
|
|
tools/__pycache__/sentiment_tool.cpython-313.pyc
ADDED
|
Binary file (3.87 kB). View file
|
|
|
tools/analytics_tool.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from crewai_tools import RagTool
|
| 2 |
+
import json
|
| 3 |
+
|
| 4 |
+
class AnalyticsTool(RagTool):
|
| 5 |
+
name: str = "analytics_tool"
|
| 6 |
+
description: str = (
|
| 7 |
+
"Processes and aggregates outputs from market, historical and sentiment agents"
|
| 8 |
+
"to generate structured indicators and performance metrics, giving a holistic view of the cryptocurrency's condition."
|
| 9 |
+
)
|
| 10 |
+
|
| 11 |
+
def _run(self, market_data: dict, historical_data: dict, sentiment_data: dict):
|
| 12 |
+
# combine results from the other agents into structured numeric metrics
|
| 13 |
+
|
| 14 |
+
try:
|
| 15 |
+
#Normalize strings -> dicts
|
| 16 |
+
if isinstance(market_data, str):
|
| 17 |
+
market_data = json.loads(market_data)
|
| 18 |
+
if isinstance(historical_data, str):
|
| 19 |
+
historical_data = json.loads(historical_data)
|
| 20 |
+
if isinstance(sentiment_data, str):
|
| 21 |
+
sentiment_data = json.loads(sentiment_data)
|
| 22 |
+
|
| 23 |
+
#Extract info safely
|
| 24 |
+
current_price = market_data.get("price") or market_data.get("latest_price")
|
| 25 |
+
pct_change = historical_data.get("pct_change", 0)
|
| 26 |
+
volatility = historical_data.get("volatility_pct", 0)
|
| 27 |
+
trend = historical_data.get("trend", "unknown")
|
| 28 |
+
sentiment = sentiment_data.get("final_sentiment_classification", "neutral")
|
| 29 |
+
|
| 30 |
+
#Compute basic consistency logic
|
| 31 |
+
aligned = (
|
| 32 |
+
(trend == "upward" and "bullish" in sentiment.lower()) or
|
| 33 |
+
(trend == "downward" and "bearish" in sentiment.lower())
|
| 34 |
+
)
|
| 35 |
+
|
| 36 |
+
score = (
|
| 37 |
+
(pct_change/10) + (0.2 if aligned else -0.2)
|
| 38 |
+
+ (0.1 if "bullish" in sentiment.lower() else -0.1 if "bearish" in sentiment.lower() else 0)
|
| 39 |
+
)
|
| 40 |
+
|
| 41 |
+
score = round(max(-1, min(1, score)), 2)
|
| 42 |
+
|
| 43 |
+
return {
|
| 44 |
+
"price": current_price,
|
| 45 |
+
"pct_change": pct_change,
|
| 46 |
+
"volatility": volatility,
|
| 47 |
+
"trend": trend,
|
| 48 |
+
"sentiment": sentiment,
|
| 49 |
+
"alignment": "aligned" if aligned else "divergent",
|
| 50 |
+
"composite_score": score,
|
| 51 |
+
"summary": f"Trend={trend}, Sentiment={sentiment}, Alignment={'aligned' if aligned else 'divergent'}, Score = {score}"
|
| 52 |
+
}
|
| 53 |
+
except Exception as e:
|
| 54 |
+
return {"error": f"AnalyticsTool failed: {str(e)}"}
|
tools/historical_data_tool.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# tools/historical_data_tool.py
|
| 2 |
+
from crewai_tools import RagTool
|
| 3 |
+
import requests
|
| 4 |
+
from datetime import datetime
|
| 5 |
+
import statistics
|
| 6 |
+
|
| 7 |
+
class HistoricalDataTool(RagTool):
|
| 8 |
+
name: str = "get_historical_data"
|
| 9 |
+
description: str = (
|
| 10 |
+
"Fetches and analyzes historical cryptocurrency market data "
|
| 11 |
+
"from the CoinGecko API for trend insights."
|
| 12 |
+
)
|
| 13 |
+
|
| 14 |
+
def _run(self, symbol: str = "bitcoin", currency: str = "usd", days: int = 30) -> dict:
|
| 15 |
+
"""
|
| 16 |
+
Fetch historical data for the specified cryptocurrency over a given number of days.
|
| 17 |
+
Computes basic trend and volatility statistics.
|
| 18 |
+
"""
|
| 19 |
+
url = f"https://api.coingecko.com/api/v3/coins/{symbol}/market_chart"
|
| 20 |
+
params = {"vs_currency": currency, "days": days}
|
| 21 |
+
|
| 22 |
+
try:
|
| 23 |
+
response = requests.get(url, params=params, timeout=10)
|
| 24 |
+
response.raise_for_status()
|
| 25 |
+
data = response.json()
|
| 26 |
+
|
| 27 |
+
prices = data.get("prices", [])
|
| 28 |
+
if not prices:
|
| 29 |
+
return {"error": f"No data found for {symbol}."}
|
| 30 |
+
|
| 31 |
+
# Extract date and price
|
| 32 |
+
history = [
|
| 33 |
+
{"date": datetime.utcfromtimestamp(p[0] / 1000).strftime("%Y-%m-%d"), "price": p[1]}
|
| 34 |
+
for p in prices
|
| 35 |
+
]
|
| 36 |
+
|
| 37 |
+
# Calculate simple stats
|
| 38 |
+
first, last = history[0]["price"], history[-1]["price"]
|
| 39 |
+
pct_change = ((last - first) / first) * 100
|
| 40 |
+
daily_returns = [
|
| 41 |
+
(history[i + 1]["price"] - history[i]["price"]) / history[i]["price"]
|
| 42 |
+
for i in range(len(history) - 1)
|
| 43 |
+
]
|
| 44 |
+
volatility = statistics.stdev(daily_returns) * 100 if len(daily_returns) > 1 else 0
|
| 45 |
+
|
| 46 |
+
trend = "upward" if pct_change > 0 else "downward" if pct_change < 0 else "sideways"
|
| 47 |
+
|
| 48 |
+
return {
|
| 49 |
+
"symbol": symbol,
|
| 50 |
+
"currency": currency,
|
| 51 |
+
"days": days,
|
| 52 |
+
"start_price": round(first, 2),
|
| 53 |
+
"end_price": round(last, 2),
|
| 54 |
+
"pct_change": round(pct_change, 2),
|
| 55 |
+
"volatility_pct": round(volatility, 2),
|
| 56 |
+
"trend": trend,
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
except Exception as e:
|
| 60 |
+
return {"error": str(e)}
|
tools/market_data.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from crewai_tools import RagTool
|
| 3 |
+
import requests
|
| 4 |
+
|
| 5 |
+
class MarketDataTool(RagTool):
|
| 6 |
+
name: str ="get_market_data"
|
| 7 |
+
description: str ="Fetches the current market price of a cryptocurrency in a chosen fiat currency using the CoinGecko API."
|
| 8 |
+
|
| 9 |
+
def _run(self, symbol="bitcoin", currency="usd"):
|
| 10 |
+
url = f"https://api.coingecko.com/api/v3/simple/price?ids={symbol}&vs_currencies={currency}"
|
| 11 |
+
try:
|
| 12 |
+
response = requests.get(url, timeout=10)
|
| 13 |
+
response.raise_for_status()
|
| 14 |
+
data = response.json()
|
| 15 |
+
price = data.get(symbol, {}).get(currency)
|
| 16 |
+
if price is None:
|
| 17 |
+
return {
|
| 18 |
+
"symbol": symbol,
|
| 19 |
+
"currency": currency,
|
| 20 |
+
"error": f"Could not find data for {symbol.upper()} in {currency.upper()}."
|
| 21 |
+
}
|
| 22 |
+
return {
|
| 23 |
+
"symbol": symbol,
|
| 24 |
+
"currency": currency,
|
| 25 |
+
"latest_price": price,
|
| 26 |
+
"formatted": f"{symbol.capitalize()} price: {price} {currency.upper()}"
|
| 27 |
+
}
|
| 28 |
+
except Exception as e:
|
| 29 |
+
return {"error": str(e)}
|
| 30 |
+
|
| 31 |
+
|
tools/sentiment_tool.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import requests
|
| 3 |
+
from crewai_tools import RagTool
|
| 4 |
+
from openai import OpenAI
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
from dotenv import load_dotenv
|
| 7 |
+
|
| 8 |
+
env_path = Path("/Users/clydecossey/Documents/Python/Crypto_Analysis_Agent/Open_AI.env")
|
| 9 |
+
load_dotenv(dotenv_path=env_path)
|
| 10 |
+
OPENAI_API_KEY = os.getenv(key = "OPENAI_API_KEY")
|
| 11 |
+
|
| 12 |
+
client = OpenAI(api_key=OPENAI_API_KEY)
|
| 13 |
+
|
| 14 |
+
serper_env_path = Path("Users/clydecossey/Documents/Python/Crypto_Analysis_Agent/Serper.env")
|
| 15 |
+
load_dotenv(dotenv_path=serper_env_path)
|
| 16 |
+
SERPER_API_KEY = os.getenv(key="SERPER_API_KEY")
|
| 17 |
+
|
| 18 |
+
class SentimentTool(RagTool):
|
| 19 |
+
name: str = "get_crypto_sentiment"
|
| 20 |
+
description: str = (
|
| 21 |
+
"Fetches recent cryptocurrency news and Reddit discussions,"
|
| 22 |
+
"then analyses sentiment as bullish, bearish or neutral."
|
| 23 |
+
)
|
| 24 |
+
|
| 25 |
+
def _run(self, query: str = "bitcoin") -> str:
|
| 26 |
+
try:
|
| 27 |
+
#Google news sentiment
|
| 28 |
+
news_url = "https://google.serper.dev/news"
|
| 29 |
+
headers = {"X-API-KEY": SERPER_API_KEY, "Content-Type": "application/json"}
|
| 30 |
+
payload = {"q": f"{query} crypto", "num": 5}
|
| 31 |
+
news_response = requests.post(news_url, headers=headers, json=payload, timout=10)
|
| 32 |
+
news = news_response.json().get("news", [])
|
| 33 |
+
headlines = [n["title"] for n in news[:5]]
|
| 34 |
+
|
| 35 |
+
#Reddit sentiment
|
| 36 |
+
reddit_url = f"https://www.reddit.com/search.json?q={query}&sort=new&limit=5"
|
| 37 |
+
reddit_headers = {"User-Agent": "CryptoAgent/1.0"}
|
| 38 |
+
reddit_response = requests.get(reddit_url,headers=reddit_headers, timeout=10)
|
| 39 |
+
reddit_posts = reddit_response.json().get("data", {}).get("children", [])
|
| 40 |
+
reddit_titles = [p["data"]["title"] for p in reddit_posts[:5]]
|
| 41 |
+
|
| 42 |
+
combined_text = "News: " + " | ".join(headlines) + "\nReddit: " + " | ".join(reddit_titles)
|
| 43 |
+
|
| 44 |
+
#analyze sentiment
|
| 45 |
+
sentiment_prompt = f"""
|
| 46 |
+
You are a crypto sentiment analyst. Based on the following headlines and Reddit posts,
|
| 47 |
+
intuitively determine if the overall sentiment towards "{query}" is bullish, bearish, or neutral.
|
| 48 |
+
Respond with a short summary and a one-word sentiment classification.
|
| 49 |
+
|
| 50 |
+
{combined_text}
|
| 51 |
+
"""
|
| 52 |
+
completion = client.chat.completions.create(
|
| 53 |
+
model="gpt-5-mini",
|
| 54 |
+
messages=[
|
| 55 |
+
{"role": "system", "content": "You are a precise sentiment classifier."},
|
| 56 |
+
{"role": "user", "content": sentiment_prompt}
|
| 57 |
+
],
|
| 58 |
+
temperature=0.2
|
| 59 |
+
)
|
| 60 |
+
sentiment_result = completion.choices[0].message.content.strip()
|
| 61 |
+
return sentiment_result
|
| 62 |
+
|
| 63 |
+
except Exception as e:
|
| 64 |
+
return f"Error fetching sentiment data: {str(e)}"
|
| 65 |
+
|