|
|
|
|
|
""" |
|
|
جمعآوری احساسات بازار از منابع رایگان |
|
|
Free Market Sentiment Collectors - NO API KEY |
|
|
""" |
|
|
|
|
|
import asyncio |
|
|
import httpx |
|
|
from typing import Dict, Optional |
|
|
from datetime import datetime |
|
|
import logging |
|
|
|
|
|
logging.basicConfig(level=logging.INFO) |
|
|
logger = logging.getLogger(__name__) |
|
|
|
|
|
|
|
|
class SentimentCollector: |
|
|
"""جمعآوری احساسات بازار از منابع رایگان""" |
|
|
|
|
|
def __init__(self): |
|
|
self.timeout = httpx.Timeout(15.0) |
|
|
self.headers = { |
|
|
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", |
|
|
"Accept": "application/json" |
|
|
} |
|
|
|
|
|
async def collect_fear_greed_index(self) -> Optional[Dict]: |
|
|
""" |
|
|
Alternative.me Crypto Fear & Greed Index |
|
|
FREE - No API key needed |
|
|
""" |
|
|
try: |
|
|
url = "https://api.alternative.me/fng/" |
|
|
|
|
|
async with httpx.AsyncClient(timeout=self.timeout) as client: |
|
|
response = await client.get(url, headers=self.headers) |
|
|
|
|
|
if response.status_code == 200: |
|
|
data = response.json() |
|
|
|
|
|
if "data" in data and data["data"]: |
|
|
fng = data["data"][0] |
|
|
|
|
|
result = { |
|
|
"fear_greed_value": int(fng.get("value", 50)), |
|
|
"fear_greed_classification": fng.get("value_classification", "Neutral"), |
|
|
"timestamp_fng": fng.get("timestamp"), |
|
|
"source": "alternative.me", |
|
|
"timestamp": datetime.now().isoformat() |
|
|
} |
|
|
|
|
|
logger.info(f"✅ Fear & Greed: {result['fear_greed_value']} ({result['fear_greed_classification']})") |
|
|
return result |
|
|
else: |
|
|
logger.warning("⚠️ Fear & Greed API returned no data") |
|
|
return None |
|
|
else: |
|
|
logger.warning(f"⚠️ Fear & Greed returned status {response.status_code}") |
|
|
return None |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"❌ Fear & Greed error: {e}") |
|
|
return None |
|
|
|
|
|
async def collect_bitcoin_dominance(self) -> Optional[Dict]: |
|
|
""" |
|
|
Bitcoin Dominance from CoinCap |
|
|
FREE - No API key needed |
|
|
""" |
|
|
try: |
|
|
url = "https://api.coincap.io/v2/assets" |
|
|
params = {"limit": 10} |
|
|
|
|
|
async with httpx.AsyncClient(timeout=self.timeout) as client: |
|
|
response = await client.get(url, params=params, headers=self.headers) |
|
|
|
|
|
if response.status_code == 200: |
|
|
data = response.json() |
|
|
assets = data.get("data", []) |
|
|
|
|
|
if not assets: |
|
|
return None |
|
|
|
|
|
|
|
|
total_market_cap = sum( |
|
|
float(asset.get("marketCapUsd", 0)) |
|
|
for asset in assets |
|
|
if asset.get("marketCapUsd") |
|
|
) |
|
|
|
|
|
|
|
|
btc = next((a for a in assets if a["symbol"] == "BTC"), None) |
|
|
if not btc: |
|
|
return None |
|
|
|
|
|
btc_market_cap = float(btc.get("marketCapUsd", 0)) |
|
|
|
|
|
|
|
|
btc_dominance = (btc_market_cap / total_market_cap * 100) if total_market_cap > 0 else 0 |
|
|
|
|
|
result = { |
|
|
"btc_dominance": round(btc_dominance, 2), |
|
|
"btc_market_cap": btc_market_cap, |
|
|
"total_market_cap": total_market_cap, |
|
|
"source": "coincap.io", |
|
|
"timestamp": datetime.now().isoformat() |
|
|
} |
|
|
|
|
|
logger.info(f"✅ BTC Dominance: {result['btc_dominance']}%") |
|
|
return result |
|
|
else: |
|
|
logger.warning(f"⚠️ CoinCap returned status {response.status_code}") |
|
|
return None |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"❌ BTC Dominance error: {e}") |
|
|
return None |
|
|
|
|
|
async def collect_global_market_stats(self) -> Optional[Dict]: |
|
|
""" |
|
|
Global Market Statistics from CoinGecko |
|
|
FREE - No API key for this endpoint |
|
|
""" |
|
|
try: |
|
|
url = "https://api.coingecko.com/api/v3/global" |
|
|
|
|
|
async with httpx.AsyncClient(timeout=self.timeout) as client: |
|
|
response = await client.get(url, headers=self.headers) |
|
|
|
|
|
if response.status_code == 200: |
|
|
data = response.json() |
|
|
global_data = data.get("data", {}) |
|
|
|
|
|
if not global_data: |
|
|
return None |
|
|
|
|
|
result = { |
|
|
"total_market_cap_usd": global_data.get("total_market_cap", {}).get("usd", 0), |
|
|
"total_volume_24h_usd": global_data.get("total_volume", {}).get("usd", 0), |
|
|
"btc_dominance": global_data.get("market_cap_percentage", {}).get("btc", 0), |
|
|
"eth_dominance": global_data.get("market_cap_percentage", {}).get("eth", 0), |
|
|
"active_cryptocurrencies": global_data.get("active_cryptocurrencies", 0), |
|
|
"markets": global_data.get("markets", 0), |
|
|
"market_cap_change_24h": global_data.get("market_cap_change_percentage_24h_usd", 0), |
|
|
"source": "coingecko.com", |
|
|
"timestamp": datetime.now().isoformat() |
|
|
} |
|
|
|
|
|
logger.info(f"✅ Global Stats: ${result['total_market_cap_usd']:,.0f} market cap") |
|
|
return result |
|
|
else: |
|
|
logger.warning(f"⚠️ CoinGecko global returned status {response.status_code}") |
|
|
return None |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"❌ Global Stats error: {e}") |
|
|
return None |
|
|
|
|
|
async def calculate_market_sentiment( |
|
|
self, |
|
|
fear_greed: Optional[Dict], |
|
|
btc_dominance: Optional[Dict], |
|
|
global_stats: Optional[Dict] |
|
|
) -> Dict: |
|
|
""" |
|
|
محاسبه احساسات کلی بازار |
|
|
Calculate overall market sentiment from multiple indicators |
|
|
""" |
|
|
sentiment_score = 50 |
|
|
confidence = 0.0 |
|
|
indicators_count = 0 |
|
|
|
|
|
sentiment_signals = [] |
|
|
|
|
|
|
|
|
if fear_greed: |
|
|
fg_value = fear_greed.get("fear_greed_value", 50) |
|
|
sentiment_score += (fg_value - 50) * 0.4 |
|
|
confidence += 0.4 |
|
|
indicators_count += 1 |
|
|
|
|
|
sentiment_signals.append({ |
|
|
"indicator": "fear_greed", |
|
|
"value": fg_value, |
|
|
"signal": fear_greed.get("fear_greed_classification") |
|
|
}) |
|
|
|
|
|
|
|
|
if btc_dominance: |
|
|
dom_value = btc_dominance.get("btc_dominance", 45) |
|
|
|
|
|
|
|
|
|
|
|
dom_score = 100 - dom_value |
|
|
sentiment_score += (dom_score - 50) * 0.3 |
|
|
confidence += 0.3 |
|
|
indicators_count += 1 |
|
|
|
|
|
sentiment_signals.append({ |
|
|
"indicator": "btc_dominance", |
|
|
"value": dom_value, |
|
|
"signal": "Defensive" if dom_value > 50 else "Risk-On" |
|
|
}) |
|
|
|
|
|
|
|
|
if global_stats: |
|
|
mc_change = global_stats.get("market_cap_change_24h", 0) |
|
|
|
|
|
|
|
|
mc_score = 50 + (mc_change * 5) |
|
|
mc_score = max(0, min(100, mc_score)) |
|
|
|
|
|
sentiment_score += (mc_score - 50) * 0.3 |
|
|
confidence += 0.3 |
|
|
indicators_count += 1 |
|
|
|
|
|
sentiment_signals.append({ |
|
|
"indicator": "market_cap_change_24h", |
|
|
"value": mc_change, |
|
|
"signal": "Bullish" if mc_change > 0 else "Bearish" |
|
|
}) |
|
|
|
|
|
|
|
|
sentiment_score = max(0, min(100, sentiment_score)) |
|
|
|
|
|
|
|
|
if sentiment_score >= 75: |
|
|
classification = "Extreme Greed" |
|
|
elif sentiment_score >= 60: |
|
|
classification = "Greed" |
|
|
elif sentiment_score >= 45: |
|
|
classification = "Neutral" |
|
|
elif sentiment_score >= 25: |
|
|
classification = "Fear" |
|
|
else: |
|
|
classification = "Extreme Fear" |
|
|
|
|
|
return { |
|
|
"overall_sentiment": classification, |
|
|
"sentiment_score": round(sentiment_score, 2), |
|
|
"confidence": round(confidence, 2), |
|
|
"indicators_used": indicators_count, |
|
|
"signals": sentiment_signals, |
|
|
"fear_greed_value": fear_greed.get("fear_greed_value") if fear_greed else None, |
|
|
"fear_greed_classification": fear_greed.get("fear_greed_classification") if fear_greed else None, |
|
|
"btc_dominance": btc_dominance.get("btc_dominance") if btc_dominance else None, |
|
|
"market_cap_change_24h": global_stats.get("market_cap_change_24h") if global_stats else None, |
|
|
"source": "aggregated", |
|
|
"timestamp": datetime.now().isoformat() |
|
|
} |
|
|
|
|
|
async def collect_all_sentiment_data(self) -> Dict: |
|
|
""" |
|
|
جمعآوری همه دادههای احساسات |
|
|
Collect ALL sentiment data and calculate overall sentiment |
|
|
""" |
|
|
logger.info("🚀 Starting collection of sentiment data...") |
|
|
|
|
|
|
|
|
fear_greed, btc_dom, global_stats = await asyncio.gather( |
|
|
self.collect_fear_greed_index(), |
|
|
self.collect_bitcoin_dominance(), |
|
|
self.collect_global_market_stats(), |
|
|
return_exceptions=True |
|
|
) |
|
|
|
|
|
|
|
|
fear_greed = fear_greed if not isinstance(fear_greed, Exception) else None |
|
|
btc_dom = btc_dom if not isinstance(btc_dom, Exception) else None |
|
|
global_stats = global_stats if not isinstance(global_stats, Exception) else None |
|
|
|
|
|
|
|
|
overall_sentiment = await self.calculate_market_sentiment( |
|
|
fear_greed, |
|
|
btc_dom, |
|
|
global_stats |
|
|
) |
|
|
|
|
|
return { |
|
|
"fear_greed": fear_greed, |
|
|
"btc_dominance": btc_dom, |
|
|
"global_stats": global_stats, |
|
|
"overall_sentiment": overall_sentiment |
|
|
} |
|
|
|
|
|
|
|
|
async def main(): |
|
|
"""Test the sentiment collectors""" |
|
|
collector = SentimentCollector() |
|
|
|
|
|
print("\n" + "="*70) |
|
|
print("🧪 Testing FREE Sentiment Collectors") |
|
|
print("="*70) |
|
|
|
|
|
|
|
|
print("\n1️⃣ Testing Fear & Greed Index...") |
|
|
fg = await collector.collect_fear_greed_index() |
|
|
if fg: |
|
|
print(f" Value: {fg['fear_greed_value']}/100") |
|
|
print(f" Classification: {fg['fear_greed_classification']}") |
|
|
|
|
|
print("\n2️⃣ Testing Bitcoin Dominance...") |
|
|
btc_dom = await collector.collect_bitcoin_dominance() |
|
|
if btc_dom: |
|
|
print(f" BTC Dominance: {btc_dom['btc_dominance']}%") |
|
|
print(f" BTC Market Cap: ${btc_dom['btc_market_cap']:,.0f}") |
|
|
|
|
|
print("\n3️⃣ Testing Global Market Stats...") |
|
|
global_stats = await collector.collect_global_market_stats() |
|
|
if global_stats: |
|
|
print(f" Total Market Cap: ${global_stats['total_market_cap_usd']:,.0f}") |
|
|
print(f" 24h Volume: ${global_stats['total_volume_24h_usd']:,.0f}") |
|
|
print(f" 24h Change: {global_stats['market_cap_change_24h']:.2f}%") |
|
|
|
|
|
|
|
|
print("\n\n" + "="*70) |
|
|
print("📊 Testing Comprehensive Sentiment Analysis") |
|
|
print("="*70) |
|
|
|
|
|
all_data = await collector.collect_all_sentiment_data() |
|
|
|
|
|
overall = all_data["overall_sentiment"] |
|
|
print(f"\n✅ Overall Market Sentiment: {overall['overall_sentiment']}") |
|
|
print(f" Sentiment Score: {overall['sentiment_score']}/100") |
|
|
print(f" Confidence: {overall['confidence']:.0%}") |
|
|
print(f" Indicators Used: {overall['indicators_used']}") |
|
|
|
|
|
print("\n📊 Individual Signals:") |
|
|
for signal in overall.get("signals", []): |
|
|
print(f" • {signal['indicator']}: {signal['value']} ({signal['signal']})") |
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
asyncio.run(main()) |
|
|
|