From 2d6d85eb2b0d7411952fb6676d4e891c61d1a84b Mon Sep 17 00:00:00 2001 From: Zhongwei Li Date: Sat, 29 Nov 2025 18:53:24 +0800 Subject: [PATCH] Initial commit --- .claude-plugin/plugin.json | 16 + README.md | 3 + commands/analyze-sentiment.md | 1393 +++++++++++++++++ plugin.lock.json | 85 + skills/skill-adapter/assets/README.md | 7 + .../skill-adapter/assets/config-template.json | 32 + skills/skill-adapter/assets/skill-schema.json | 28 + skills/skill-adapter/assets/test-data.json | 27 + skills/skill-adapter/references/README.md | 9 + .../references/best-practices.md | 69 + skills/skill-adapter/references/examples.md | 70 + skills/skill-adapter/scripts/README.md | 10 + .../skill-adapter/scripts/helper-template.sh | 42 + skills/skill-adapter/scripts/validation.sh | 32 + 14 files changed, 1823 insertions(+) create mode 100644 .claude-plugin/plugin.json create mode 100644 README.md create mode 100644 commands/analyze-sentiment.md create mode 100644 plugin.lock.json create mode 100644 skills/skill-adapter/assets/README.md create mode 100644 skills/skill-adapter/assets/config-template.json create mode 100644 skills/skill-adapter/assets/skill-schema.json create mode 100644 skills/skill-adapter/assets/test-data.json create mode 100644 skills/skill-adapter/references/README.md create mode 100644 skills/skill-adapter/references/best-practices.md create mode 100644 skills/skill-adapter/references/examples.md create mode 100644 skills/skill-adapter/scripts/README.md create mode 100755 skills/skill-adapter/scripts/helper-template.sh create mode 100755 skills/skill-adapter/scripts/validation.sh diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..a8be1eb --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,16 @@ +{ + "name": "market-sentiment-analyzer", + "description": "Analyze market sentiment from social media, news, and on-chain data", + "version": "1.0.0", + "author": { + "name": "Intent Solutions IO", + "email": "jeremy@intentsolutions.ai", + "url": "https://intentsolutions.ai" + }, + "skills": [ + "./skills" + ], + "commands": [ + "./commands" + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..e62c396 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# market-sentiment-analyzer + +Analyze market sentiment from social media, news, and on-chain data diff --git a/commands/analyze-sentiment.md b/commands/analyze-sentiment.md new file mode 100644 index 0000000..bd2c471 --- /dev/null +++ b/commands/analyze-sentiment.md @@ -0,0 +1,1393 @@ +--- +description: Analyze market sentiment from social media, news, and on-chain metrics +shortcut: as +--- + +# Analyze Market Sentiment + +Comprehensive multi-source sentiment analysis system that aggregates data from social media platforms, news outlets, on-chain metrics, derivatives markets, and whale tracking to predict market movements and identify trading opportunities. + +## Overview + +Market sentiment analysis combines quantitative on-chain data with qualitative social signals to measure the collective mood of market participants. This command provides a sophisticated sentiment aggregation framework that: + +- **Monitors 6+ data sources** including Twitter/X, Reddit, Telegram, Discord, news feeds, and blockchain metrics +- **Calculates Fear & Greed Index** using weighted multi-factor analysis +- **Analyzes derivatives markets** through funding rates and options sentiment +- **Tracks whale behavior** via large holder movements and exchange flows +- **Correlates sentiment shifts** with historical price action for predictive insights +- **Generates weighted sentiment scores** with confidence intervals and statistical significance + +The analyzer uses natural language processing (NLP), time-series analysis, and machine learning to identify sentiment extremes that often precede major price reversals, making it invaluable for contrarian trading strategies. + +## Data Sources & Weighting + +### Social Media Sentiment (35% weight) + +**Twitter/X Analysis (15%)** +- Real-time tweet monitoring for crypto-specific keywords and cashtags +- Sentiment scoring using VADER and FinBERT NLP models +- Influencer tracking with follower-weighted sentiment +- Engagement metrics (likes, retweets, quote tweets) +- Trending topics and hashtag velocity analysis + +**Reddit Sentiment (10%)** +- r/CryptoCurrency, r/Bitcoin, r/ethereum analysis +- Post and comment sentiment with upvote weighting +- Daily discussion thread analysis +- Subreddit activity levels (posts per hour) +- Cross-post momentum tracking + +**Telegram & Discord (10%)** +- Group message sentiment analysis +- Active user count and engagement rate +- Admin/moderator sentiment filtering +- Message velocity and panic indicators +- Voice chat activity monitoring + +### News & Media Sentiment (20% weight) + +**Mainstream Media Coverage** +- Automated news scraping from CoinDesk, CoinTelegraph, Bloomberg Crypto +- Headline sentiment analysis with clickbait filtering +- Source credibility scoring +- Publication frequency tracking +- Fear/uncertainty/doubt (FUD) detection + +**Regulatory News Impact** +- Government announcement tracking +- SEC filing monitoring +- Central bank statement analysis +- Regulatory sentiment classification +- Geographic regulatory heat maps + +### Derivatives Market Sentiment (25% weight) + +**Funding Rates Analysis (12%)** +- Perpetual futures funding rate tracking across exchanges +- Historical funding rate comparison +- Funding rate divergence alerts +- Long/short ratio calculation +- Liquidation cascade prediction + +**Options Market Sentiment (13%)** +- Put/Call ratio analysis for major strikes +- Implied volatility skew interpretation +- Options open interest distribution +- Max pain price calculation +- Gamma squeeze potential detection + +### Whale & Smart Money Tracking (15% weight) + +**Large Holder Movements** +- Whale wallet transaction monitoring (>$1M transfers) +- Exchange deposit/withdrawal flow analysis +- Cold wallet accumulation patterns +- Smart money address tracking +- Whale sentiment divergence from retail + +**Exchange Flow Analysis** +- Net flow (deposits minus withdrawals) +- Exchange reserve levels +- Miner selling pressure +- OTC desk activity indicators +- Stablecoin flow correlation + +### On-Chain Sentiment Indicators (5% weight) + +**Network Value Metrics** +- MVRV ratio (Market Value to Realized Value) +- NVT ratio (Network Value to Transactions) +- Spent Output Age analysis +- Long-term holder behavior +- Realized cap momentum + +## Fear & Greed Index Calculation + +The Fear & Greed Index aggregates multiple data sources into a single 0-100 score: + +**Score Interpretation:** +- 0-25: Extreme Fear (potential buying opportunity) +- 25-45: Fear (cautious sentiment) +- 45-55: Neutral (balanced market) +- 55-75: Greed (risk appetite increasing) +- 75-100: Extreme Greed (potential correction ahead) + +**Calculation Methodology:** + +1. **Social Sentiment Component (35 points)** + - Twitter sentiment: 15 points (scaled -1 to +1 → 0 to 15) + - Reddit sentiment: 10 points + - Telegram/Discord: 10 points + +2. **Market Momentum Component (20 points)** + - Price volatility (25% of component) + - Trading volume vs. 30-day average (25%) + - Market dominance trends (25%) + - Recent price action (25%) + +3. **Derivatives Component (25 points)** + - Funding rates (12 points, scaled from -0.1% to +0.1%) + - Put/Call ratio (13 points, inverted scale) + +4. **Whale Behavior Component (15 points)** + - Exchange net flow (7.5 points) + - Large transaction frequency (7.5 points) + +5. **On-Chain Component (5 points)** + - MVRV ratio deviation from mean + - NVT ratio trend + +## Code Implementation + +### Comprehensive Python Sentiment Analyzer + +```python +#!/usr/bin/env python3 +""" +Comprehensive Multi-Source Crypto Market Sentiment Analyzer +Aggregates sentiment from social media, news, derivatives, and on-chain data +""" + +import asyncio +import aiohttp +import pandas as pd +import numpy as np +from datetime import datetime, timedelta +from typing import Dict, List, Tuple, Optional +from dataclasses import dataclass, field +from enum import Enum +import statistics +import json +import re +from collections import defaultdict, deque +import tweepy +import praw +from telethon import TelegramClient +from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer +from transformers import pipeline +import ccxt.async_support as ccxt +from web3 import Web3 +import redis +import logging + +# Configure logging +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' +) +logger = logging.getLogger(__name__) + + +class SentimentLevel(Enum): + """Sentiment level classification""" + EXTREME_FEAR = "Extreme Fear" + FEAR = "Fear" + NEUTRAL = "Neutral" + GREED = "Greed" + EXTREME_GREED = "Extreme Greed" + + +@dataclass +class SentimentScore: + """Individual sentiment score with metadata""" + source: str + score: float # -1.0 to 1.0 + confidence: float # 0.0 to 1.0 + weight: float + timestamp: datetime + sample_size: int + metadata: Dict = field(default_factory=dict) + + +@dataclass +class FearGreedIndex: + """Fear & Greed Index result""" + score: int # 0-100 + level: SentimentLevel + components: Dict[str, float] + historical_percentile: float + trend: str # 'increasing', 'decreasing', 'stable' + timestamp: datetime + + +@dataclass +class SentimentAlert: + """Sentiment-based trading alert""" + alert_type: str + severity: str # 'low', 'medium', 'high', 'critical' + message: str + sentiment_score: float + trigger_condition: str + timestamp: datetime + + +class SocialMediaAnalyzer: + """Analyze sentiment from social media platforms""" + + def __init__(self, config: Dict): + self.config = config + self.vader = SentimentIntensityAnalyzer() + self.finbert = pipeline( + "sentiment-analysis", + model="ProsusAI/finbert", + device=-1 # CPU + ) + + # Initialize API clients + self.twitter_client = self._init_twitter() + self.reddit_client = self._init_reddit() + self.telegram_client = self._init_telegram() + + # Sentiment cache + self.cache = redis.Redis( + host='localhost', + port=6379, + db=0, + decode_responses=True + ) + + def _init_twitter(self) -> Optional[tweepy.Client]: + """Initialize Twitter API client""" + try: + return tweepy.Client( + bearer_token=self.config.get('twitter_bearer_token'), + wait_on_rate_limit=True + ) + except Exception as e: + logger.error(f"Twitter initialization failed: {e}") + return None + + def _init_reddit(self) -> Optional[praw.Reddit]: + """Initialize Reddit API client""" + try: + return praw.Reddit( + client_id=self.config.get('reddit_client_id'), + client_secret=self.config.get('reddit_client_secret'), + user_agent='CryptoSentimentAnalyzer/1.0' + ) + except Exception as e: + logger.error(f"Reddit initialization failed: {e}") + return None + + def _init_telegram(self) -> Optional[TelegramClient]: + """Initialize Telegram client""" + try: + return TelegramClient( + 'sentiment_session', + self.config.get('telegram_api_id'), + self.config.get('telegram_api_hash') + ) + except Exception as e: + logger.error(f"Telegram initialization failed: {e}") + return None + + async def analyze_twitter(self, symbol: str, lookback_hours: int = 24) -> SentimentScore: + """Analyze Twitter sentiment for cryptocurrency""" + if not self.twitter_client: + return self._empty_sentiment_score('twitter') + + try: + # Search for tweets + query = f"${symbol} OR #{symbol} -is:retweet lang:en" + tweets = self.twitter_client.search_recent_tweets( + query=query, + max_results=100, + tweet_fields=['created_at', 'public_metrics', 'author_id'] + ) + + if not tweets.data: + return self._empty_sentiment_score('twitter') + + sentiments = [] + total_engagement = 0 + influential_count = 0 + + for tweet in tweets.data: + # Get engagement metrics + metrics = tweet.public_metrics + engagement = ( + metrics['like_count'] + + metrics['retweet_count'] * 2 + + metrics['reply_count'] + ) + + # Calculate sentiment with VADER + vader_score = self.vader.polarity_scores(tweet.text) + + # Calculate FinBERT sentiment for financial context + try: + finbert_result = self.finbert(tweet.text[:512])[0] + finbert_score = self._finbert_to_score(finbert_result) + except Exception: + finbert_score = vader_score['compound'] + + # Weighted average of VADER and FinBERT + combined_score = 0.6 * finbert_score + 0.4 * vader_score['compound'] + + # Weight by engagement + sentiments.append({ + 'score': combined_score, + 'weight': max(1, engagement) + }) + + total_engagement += engagement + + # Track influential tweets (high engagement) + if engagement > 100: + influential_count += 1 + + # Calculate weighted average sentiment + weighted_sum = sum(s['score'] * s['weight'] for s in sentiments) + total_weight = sum(s['weight'] for s in sentiments) + avg_sentiment = weighted_sum / total_weight if total_weight > 0 else 0.0 + + # Calculate confidence based on sample size and agreement + sentiment_values = [s['score'] for s in sentiments] + std_dev = statistics.stdev(sentiment_values) if len(sentiment_values) > 1 else 1.0 + confidence = min(1.0, (len(sentiments) / 100) * (1.0 - std_dev)) + + return SentimentScore( + source='twitter', + score=avg_sentiment, + confidence=confidence, + weight=0.15, + timestamp=datetime.now(), + sample_size=len(sentiments), + metadata={ + 'total_engagement': total_engagement, + 'influential_tweets': influential_count, + 'avg_engagement': total_engagement / len(sentiments), + 'sentiment_distribution': { + 'positive': sum(1 for s in sentiments if s['score'] > 0.05), + 'neutral': sum(1 for s in sentiments if -0.05 <= s['score'] <= 0.05), + 'negative': sum(1 for s in sentiments if s['score'] < -0.05) + } + } + ) + + except Exception as e: + logger.error(f"Twitter analysis error: {e}") + return self._empty_sentiment_score('twitter') + + async def analyze_reddit(self, symbol: str, lookback_hours: int = 24) -> SentimentScore: + """Analyze Reddit sentiment for cryptocurrency""" + if not self.reddit_client: + return self._empty_sentiment_score('reddit') + + try: + # Define relevant subreddits + subreddits = ['CryptoCurrency', 'Bitcoin', 'ethereum', 'CryptoMarkets'] + + sentiments = [] + total_upvotes = 0 + post_count = 0 + comment_count = 0 + + for subreddit_name in subreddits: + subreddit = self.reddit_client.subreddit(subreddit_name) + + # Search posts + search_query = f"{symbol}" + for post in subreddit.search(search_query, time_filter='day', limit=50): + post_age_hours = (datetime.now().timestamp() - post.created_utc) / 3600 + if post_age_hours > lookback_hours: + continue + + # Analyze post title and text + text = f"{post.title} {post.selftext}" + sentiment = self.vader.polarity_scores(text)['compound'] + + # Weight by upvote ratio and score + weight = max(1, post.score * post.upvote_ratio) + + sentiments.append({ + 'score': sentiment, + 'weight': weight + }) + + total_upvotes += post.score + post_count += 1 + + # Analyze top comments + post.comment_sort = 'top' + post.comment_limit = 10 + for comment in post.comments[:10]: + if hasattr(comment, 'body'): + comment_sentiment = self.vader.polarity_scores(comment.body)['compound'] + comment_weight = max(1, comment.score) + + sentiments.append({ + 'score': comment_sentiment, + 'weight': comment_weight + }) + + comment_count += 1 + + if not sentiments: + return self._empty_sentiment_score('reddit') + + # Calculate weighted average + weighted_sum = sum(s['score'] * s['weight'] for s in sentiments) + total_weight = sum(s['weight'] for s in sentiments) + avg_sentiment = weighted_sum / total_weight if total_weight > 0 else 0.0 + + # Calculate activity level + total_items = post_count + comment_count + activity_level = ( + 'HIGH' if total_items > 100 else + 'MEDIUM' if total_items > 30 else + 'LOW' + ) + + # Confidence based on sample size + confidence = min(1.0, total_items / 100) + + return SentimentScore( + source='reddit', + score=avg_sentiment, + confidence=confidence, + weight=0.10, + timestamp=datetime.now(), + sample_size=len(sentiments), + metadata={ + 'post_count': post_count, + 'comment_count': comment_count, + 'total_upvotes': total_upvotes, + 'activity_level': activity_level, + 'avg_upvotes_per_post': total_upvotes / post_count if post_count > 0 else 0 + } + ) + + except Exception as e: + logger.error(f"Reddit analysis error: {e}") + return self._empty_sentiment_score('reddit') + + async def analyze_telegram(self, symbol: str, lookback_hours: int = 24) -> SentimentScore: + """Analyze Telegram group sentiment""" + if not self.telegram_client: + return self._empty_sentiment_score('telegram') + + try: + # Define target channels/groups + channels = self.config.get('telegram_channels', []) + + sentiments = [] + total_messages = 0 + + async with self.telegram_client: + for channel in channels: + try: + # Get recent messages + messages = await self.telegram_client.get_messages( + channel, + limit=100 + ) + + for msg in messages: + # Check message age + msg_age = datetime.now() - msg.date + if msg_age.total_seconds() > lookback_hours * 3600: + continue + + if msg.text and symbol.lower() in msg.text.lower(): + sentiment = self.vader.polarity_scores(msg.text)['compound'] + + # Weight by reactions/views + weight = 1 + if msg.reactions: + weight += sum(r.count for r in msg.reactions.results) + + sentiments.append({ + 'score': sentiment, + 'weight': weight + }) + + total_messages += 1 + + except Exception as e: + logger.warning(f"Error accessing channel {channel}: {e}") + continue + + if not sentiments: + return self._empty_sentiment_score('telegram') + + # Calculate weighted average + weighted_sum = sum(s['score'] * s['weight'] for s in sentiments) + total_weight = sum(s['weight'] for s in sentiments) + avg_sentiment = weighted_sum / total_weight if total_weight > 0 else 0.0 + + confidence = min(1.0, total_messages / 50) + + return SentimentScore( + source='telegram', + score=avg_sentiment, + confidence=confidence, + weight=0.10, + timestamp=datetime.now(), + sample_size=total_messages, + metadata={ + 'channels_analyzed': len(channels), + 'total_messages': total_messages + } + ) + + except Exception as e: + logger.error(f"Telegram analysis error: {e}") + return self._empty_sentiment_score('telegram') + + def _finbert_to_score(self, result: Dict) -> float: + """Convert FinBERT result to -1 to 1 score""" + label = result['label'].lower() + score = result['score'] + + if label == 'positive': + return score + elif label == 'negative': + return -score + else: # neutral + return 0.0 + + def _empty_sentiment_score(self, source: str) -> SentimentScore: + """Return empty sentiment score""" + return SentimentScore( + source=source, + score=0.0, + confidence=0.0, + weight=0.0, + timestamp=datetime.now(), + sample_size=0, + metadata={} + ) + + +class DerivativesAnalyzer: + """Analyze derivatives market sentiment""" + + def __init__(self): + self.exchanges = { + 'binance': ccxt.binance(), + 'bybit': ccxt.bybit(), + 'okx': ccxt.okx() + } + + async def analyze_funding_rates(self, symbol: str) -> SentimentScore: + """Analyze perpetual futures funding rates""" + try: + funding_rates = [] + + for exchange_name, exchange in self.exchanges.items(): + try: + # Get funding rate + ticker = await exchange.fetch_ticker(f"{symbol}/USDT:USDT") + + if 'fundingRate' in ticker['info']: + rate = float(ticker['info']['fundingRate']) + funding_rates.append({ + 'exchange': exchange_name, + 'rate': rate + }) + + except Exception as e: + logger.warning(f"Error fetching funding rate from {exchange_name}: {e}") + continue + + if not funding_rates: + return self._empty_sentiment_score('funding_rates') + + # Calculate average funding rate + avg_rate = statistics.mean([fr['rate'] for fr in funding_rates]) + + # Convert funding rate to sentiment score + # Positive funding = longs pay shorts = bullish = positive sentiment + # Negative funding = shorts pay longs = bearish = negative sentiment + # Normalize to -1 to 1 scale (typical range: -0.1% to +0.1%) + sentiment_score = np.clip(avg_rate * 1000, -1.0, 1.0) + + # High absolute funding rates indicate extreme sentiment + confidence = min(1.0, abs(avg_rate) * 5000) + + return SentimentScore( + source='funding_rates', + score=sentiment_score, + confidence=confidence, + weight=0.12, + timestamp=datetime.now(), + sample_size=len(funding_rates), + metadata={ + 'avg_funding_rate': avg_rate, + 'rate_percentage': avg_rate * 100, + 'exchanges': funding_rates, + 'interpretation': ( + 'Strong bullish bias' if avg_rate > 0.0005 else + 'Mild bullish bias' if avg_rate > 0.0001 else + 'Neutral' if abs(avg_rate) <= 0.0001 else + 'Mild bearish bias' if avg_rate > -0.0005 else + 'Strong bearish bias' + ) + } + ) + + except Exception as e: + logger.error(f"Funding rate analysis error: {e}") + return self._empty_sentiment_score('funding_rates') + + finally: + # Close exchange connections + for exchange in self.exchanges.values(): + await exchange.close() + + async def analyze_options_sentiment(self, symbol: str) -> SentimentScore: + """Analyze options market sentiment (put/call ratio, max pain)""" + try: + # For demonstration - would integrate with Deribit API + # This is a simplified simulation + + # Fetch options data (simulated) + put_volume = 15000 + call_volume = 22000 + put_call_ratio = put_volume / call_volume + + # Calculate sentiment from put/call ratio + # Low ratio (< 0.7) = bullish (more calls) = positive sentiment + # High ratio (> 1.3) = bearish (more puts) = negative sentiment + if put_call_ratio < 0.7: + sentiment_score = (0.7 - put_call_ratio) / 0.7 # 0 to 1 + elif put_call_ratio > 1.3: + sentiment_score = -(put_call_ratio - 1.3) / 1.3 # -1 to 0 + else: + # Neutral range + sentiment_score = 0.0 + + sentiment_score = np.clip(sentiment_score, -1.0, 1.0) + + # Confidence based on volume + total_volume = put_volume + call_volume + confidence = min(1.0, total_volume / 50000) + + return SentimentScore( + source='options', + score=sentiment_score, + confidence=confidence, + weight=0.13, + timestamp=datetime.now(), + sample_size=1, + metadata={ + 'put_call_ratio': put_call_ratio, + 'put_volume': put_volume, + 'call_volume': call_volume, + 'interpretation': ( + 'Bullish (heavy call buying)' if put_call_ratio < 0.7 else + 'Neutral' if 0.7 <= put_call_ratio <= 1.3 else + 'Bearish (heavy put buying)' + ) + } + ) + + except Exception as e: + logger.error(f"Options sentiment analysis error: {e}") + return self._empty_sentiment_score('options') + + def _empty_sentiment_score(self, source: str) -> SentimentScore: + """Return empty sentiment score""" + return SentimentScore( + source=source, + score=0.0, + confidence=0.0, + weight=0.0, + timestamp=datetime.now(), + sample_size=0, + metadata={} + ) + + +class OnChainAnalyzer: + """Analyze on-chain sentiment indicators""" + + def __init__(self, config: Dict): + self.config = config + # Initialize Web3 connections for different chains + self.w3_eth = Web3(Web3.HTTPProvider(config.get('ethereum_rpc'))) + self.session = None + + async def _get_session(self) -> aiohttp.ClientSession: + """Get or create aiohttp session""" + if self.session is None or self.session.closed: + self.session = aiohttp.ClientSession() + return self.session + + async def analyze_whale_movements(self, symbol: str) -> SentimentScore: + """Analyze large holder movements""" + try: + session = await self._get_session() + + # Use Whale Alert API or similar service + whale_api_url = "https://api.whale-alert.io/v1/transactions" + params = { + 'api_key': self.config.get('whale_alert_api_key'), + 'min_value': 1000000, # $1M minimum + 'symbol': symbol.lower(), + 'limit': 100 + } + + async with session.get(whale_api_url, params=params) as response: + if response.status != 200: + return self._empty_sentiment_score('whale_movements') + + data = await response.json() + transactions = data.get('transactions', []) + + if not transactions: + return self._empty_sentiment_score('whale_movements') + + # Analyze transaction patterns + exchange_deposits = 0 # Bearish + exchange_withdrawals = 0 # Bullish + unknown_transfers = 0 + + total_value = 0 + + for tx in transactions: + value = tx.get('amount_usd', 0) + total_value += value + + from_type = tx.get('from', {}).get('owner_type', 'unknown') + to_type = tx.get('to', {}).get('owner_type', 'unknown') + + if to_type == 'exchange': + exchange_deposits += value + elif from_type == 'exchange': + exchange_withdrawals += value + else: + unknown_transfers += value + + # Calculate net exchange flow sentiment + net_flow = exchange_withdrawals - exchange_deposits + + # Normalize to -1 to 1 scale + max_value = max(exchange_deposits, exchange_withdrawals) + if max_value > 0: + sentiment_score = net_flow / max_value + else: + sentiment_score = 0.0 + + sentiment_score = np.clip(sentiment_score, -1.0, 1.0) + + # Confidence based on transaction count and value + confidence = min(1.0, (len(transactions) / 100) * (total_value / 100000000)) + + return SentimentScore( + source='whale_movements', + score=sentiment_score, + confidence=confidence, + weight=0.15, + timestamp=datetime.now(), + sample_size=len(transactions), + metadata={ + 'exchange_deposits_usd': exchange_deposits, + 'exchange_withdrawals_usd': exchange_withdrawals, + 'net_flow_usd': net_flow, + 'total_value_usd': total_value, + 'transaction_count': len(transactions), + 'interpretation': ( + 'Strong accumulation (whale withdrawals)' if sentiment_score > 0.5 else + 'Mild accumulation' if sentiment_score > 0.2 else + 'Neutral' if abs(sentiment_score) <= 0.2 else + 'Mild distribution' if sentiment_score > -0.5 else + 'Strong distribution (whale deposits)' + ) + } + ) + + except Exception as e: + logger.error(f"Whale movement analysis error: {e}") + return self._empty_sentiment_score('whale_movements') + + async def analyze_network_value_metrics(self, symbol: str) -> SentimentScore: + """Analyze MVRV, NVT, and other network value metrics""" + try: + # For demonstration - would integrate with Glassnode, CoinMetrics, etc. + # This is a simplified simulation + + # MVRV ratio (Market Value to Realized Value) + # > 3.0 = overvalued/euphoric (negative sentiment) + # < 1.0 = undervalued/despair (positive sentiment for contrarian) + mvrv_ratio = 2.1 + + # NVT ratio (Network Value to Transactions) + # High NVT = overvalued relative to usage + nvt_ratio = 85 + + # Calculate sentiment from MVRV + # Convert to -1 to 1 scale where extremes indicate reversal potential + if mvrv_ratio > 3.0: + # Overvalued - bearish + mvrv_sentiment = -(min(mvrv_ratio - 3.0, 2.0) / 2.0) + elif mvrv_ratio < 1.0: + # Undervalued - bullish (contrarian) + mvrv_sentiment = (1.0 - mvrv_ratio) + else: + # Neutral range (1.0 to 3.0) + mvrv_sentiment = (2.0 - mvrv_ratio) / 2.0 + + # Calculate sentiment from NVT (inverse relationship) + # Low NVT = healthy usage = bullish + # High NVT = overvalued = bearish + if nvt_ratio > 100: + nvt_sentiment = -0.5 + elif nvt_ratio < 50: + nvt_sentiment = 0.5 + else: + nvt_sentiment = 0.0 + + # Combine metrics + combined_sentiment = (mvrv_sentiment * 0.6 + nvt_sentiment * 0.4) + combined_sentiment = np.clip(combined_sentiment, -1.0, 1.0) + + # Confidence is moderate for on-chain metrics + confidence = 0.7 + + return SentimentScore( + source='network_metrics', + score=combined_sentiment, + confidence=confidence, + weight=0.05, + timestamp=datetime.now(), + sample_size=1, + metadata={ + 'mvrv_ratio': mvrv_ratio, + 'nvt_ratio': nvt_ratio, + 'mvrv_interpretation': ( + 'Extremely overvalued' if mvrv_ratio > 3.5 else + 'Overvalued' if mvrv_ratio > 2.5 else + 'Fair value' if 1.0 <= mvrv_ratio <= 2.5 else + 'Undervalued' + ), + 'nvt_interpretation': ( + 'Overvalued vs usage' if nvt_ratio > 100 else + 'Fair valuation' if 50 <= nvt_ratio <= 100 else + 'Undervalued vs usage' + ) + } + ) + + except Exception as e: + logger.error(f"Network value metrics analysis error: {e}") + return self._empty_sentiment_score('network_metrics') + + def _empty_sentiment_score(self, source: str) -> SentimentScore: + """Return empty sentiment score""" + return SentimentScore( + source=source, + score=0.0, + confidence=0.0, + weight=0.0, + timestamp=datetime.now(), + sample_size=0, + metadata={} + ) + + async def close(self): + """Close aiohttp session""" + if self.session and not self.session.closed: + await self.session.close() + + +class MarketSentimentAggregator: + """Aggregate all sentiment sources into unified score""" + + def __init__(self, config: Dict): + self.config = config + self.social_analyzer = SocialMediaAnalyzer(config) + self.derivatives_analyzer = DerivativesAnalyzer() + self.onchain_analyzer = OnChainAnalyzer(config) + + # Historical sentiment tracking + self.sentiment_history = deque(maxlen=168) # 7 days of hourly data + + async def analyze_complete_sentiment(self, symbol: str) -> Dict: + """Run complete sentiment analysis""" + logger.info(f"Starting comprehensive sentiment analysis for {symbol}") + + # Gather all sentiment sources concurrently + tasks = [ + self.social_analyzer.analyze_twitter(symbol), + self.social_analyzer.analyze_reddit(symbol), + self.social_analyzer.analyze_telegram(symbol), + self.derivatives_analyzer.analyze_funding_rates(symbol), + self.derivatives_analyzer.analyze_options_sentiment(symbol), + self.onchain_analyzer.analyze_whale_movements(symbol), + self.onchain_analyzer.analyze_network_value_metrics(symbol) + ] + + sentiment_scores = await asyncio.gather(*tasks, return_exceptions=True) + + # Filter out exceptions + valid_scores = [ + score for score in sentiment_scores + if isinstance(score, SentimentScore) and score.confidence > 0 + ] + + if not valid_scores: + logger.warning("No valid sentiment scores obtained") + return self._empty_analysis_result(symbol) + + # Calculate weighted aggregate sentiment + aggregate_sentiment = self._calculate_weighted_sentiment(valid_scores) + + # Calculate Fear & Greed Index + fear_greed_index = self._calculate_fear_greed_index(valid_scores, aggregate_sentiment) + + # Generate sentiment alerts + alerts = self._generate_alerts(valid_scores, fear_greed_index) + + # Store in history + self.sentiment_history.append({ + 'timestamp': datetime.now(), + 'sentiment': aggregate_sentiment, + 'fear_greed_score': fear_greed_index.score + }) + + # Calculate trend + trend = self._calculate_trend() + + # Perform correlation analysis with historical price + correlation_analysis = await self._analyze_sentiment_price_correlation(symbol) + + result = { + 'symbol': symbol, + 'timestamp': datetime.now().isoformat(), + 'aggregate_sentiment': { + 'score': aggregate_sentiment, + 'normalized_score': (aggregate_sentiment + 1) / 2, # 0 to 1 scale + 'interpretation': self._interpret_sentiment(aggregate_sentiment) + }, + 'fear_greed_index': { + 'score': fear_greed_index.score, + 'level': fear_greed_index.level.value, + 'historical_percentile': fear_greed_index.historical_percentile, + 'trend': trend, + 'components': fear_greed_index.components + }, + 'sentiment_sources': [ + { + 'source': score.source, + 'score': score.score, + 'confidence': score.confidence, + 'weight': score.weight, + 'sample_size': score.sample_size, + 'metadata': score.metadata + } + for score in valid_scores + ], + 'alerts': [ + { + 'type': alert.alert_type, + 'severity': alert.severity, + 'message': alert.message, + 'sentiment_score': alert.sentiment_score, + 'trigger': alert.trigger_condition + } + for alert in alerts + ], + 'correlation_analysis': correlation_analysis, + 'data_quality': { + 'sources_analyzed': len(valid_scores), + 'total_samples': sum(score.sample_size for score in valid_scores), + 'avg_confidence': statistics.mean([score.confidence for score in valid_scores]), + 'coverage_percentage': (len(valid_scores) / 7) * 100 # 7 possible sources + } + } + + logger.info(f"Sentiment analysis complete: {fear_greed_index.level.value} " + f"(score: {fear_greed_index.score})") + + return result + + def _calculate_weighted_sentiment(self, scores: List[SentimentScore]) -> float: + """Calculate weighted average sentiment""" + weighted_sum = sum( + score.score * score.weight * score.confidence + for score in scores + ) + total_weight = sum( + score.weight * score.confidence + for score in scores + ) + + if total_weight == 0: + return 0.0 + + return weighted_sum / total_weight + + def _calculate_fear_greed_index( + self, + scores: List[SentimentScore], + aggregate_sentiment: float + ) -> FearGreedIndex: + """Calculate Fear & Greed Index (0-100)""" + + # Start with aggregate sentiment scaled to 0-100 + base_score = (aggregate_sentiment + 1) * 50 + + # Build component scores + components = {} + + for score in scores: + # Scale each source to 0-100 + component_score = (score.score + 1) * 50 + components[score.source] = component_score + + # Calculate historical percentile + if len(self.sentiment_history) > 10: + historical_scores = [h['fear_greed_score'] for h in self.sentiment_history] + percentile = ( + sum(1 for s in historical_scores if s < base_score) / + len(historical_scores) * 100 + ) + else: + percentile = 50.0 # Default to median if insufficient history + + # Determine sentiment level + if base_score >= 75: + level = SentimentLevel.EXTREME_GREED + elif base_score >= 55: + level = SentimentLevel.GREED + elif base_score >= 45: + level = SentimentLevel.NEUTRAL + elif base_score >= 25: + level = SentimentLevel.FEAR + else: + level = SentimentLevel.EXTREME_FEAR + + return FearGreedIndex( + score=int(base_score), + level=level, + components=components, + historical_percentile=percentile, + trend='', # Calculated separately + timestamp=datetime.now() + ) + + def _calculate_trend(self) -> str: + """Calculate sentiment trend from history""" + if len(self.sentiment_history) < 5: + return 'insufficient_data' + + # Compare recent average to older average + recent_scores = [h['fear_greed_score'] for h in list(self.sentiment_history)[-5:]] + older_scores = [h['fear_greed_score'] for h in list(self.sentiment_history)[-10:-5]] + + recent_avg = statistics.mean(recent_scores) + older_avg = statistics.mean(older_scores) + + diff = recent_avg - older_avg + + if diff > 5: + return 'increasing' + elif diff < -5: + return 'decreasing' + else: + return 'stable' + + def _generate_alerts( + self, + scores: List[SentimentScore], + fear_greed: FearGreedIndex + ) -> List[SentimentAlert]: + """Generate trading alerts based on sentiment extremes""" + alerts = [] + + # Extreme Fear alert (potential buying opportunity) + if fear_greed.level == SentimentLevel.EXTREME_FEAR: + alerts.append(SentimentAlert( + alert_type='contrarian_buy_opportunity', + severity='high', + message=f'Extreme Fear detected (score: {fear_greed.score}). ' + 'Historically, this has preceded price rebounds.', + sentiment_score=fear_greed.score, + trigger_condition='fear_greed_score < 25', + timestamp=datetime.now() + )) + + # Extreme Greed alert (potential selling opportunity) + if fear_greed.level == SentimentLevel.EXTREME_GREED: + alerts.append(SentimentAlert( + alert_type='contrarian_sell_opportunity', + severity='high', + message=f'Extreme Greed detected (score: {fear_greed.score}). ' + 'Market may be overheated - consider profit taking.', + sentiment_score=fear_greed.score, + trigger_condition='fear_greed_score > 75', + timestamp=datetime.now() + )) + + # Funding rate extreme alert + funding_score = next( + (s for s in scores if s.source == 'funding_rates'), + None + ) + if funding_score and abs(funding_score.score) > 0.7: + direction = 'long' if funding_score.score > 0 else 'short' + alerts.append(SentimentAlert( + alert_type='funding_rate_extreme', + severity='medium', + message=f'Extreme {direction} funding rate detected. ' + f'Potential for {direction} squeeze or reversal.', + sentiment_score=funding_score.score, + trigger_condition=f'abs(funding_rate_score) > 0.7', + timestamp=datetime.now() + )) + + # Whale movement alert + whale_score = next( + (s for s in scores if s.source == 'whale_movements'), + None + ) + if whale_score and abs(whale_score.score) > 0.5: + direction = 'accumulation' if whale_score.score > 0 else 'distribution' + alerts.append(SentimentAlert( + alert_type='whale_activity', + severity='medium', + message=f'Significant whale {direction} detected. ' + f'Smart money may be positioning for trend.', + sentiment_score=whale_score.score, + trigger_condition='abs(whale_score) > 0.5', + timestamp=datetime.now() + )) + + return alerts + + async def _analyze_sentiment_price_correlation(self, symbol: str) -> Dict: + """Analyze correlation between sentiment and price movements""" + try: + if len(self.sentiment_history) < 20: + return { + 'status': 'insufficient_data', + 'message': 'Need at least 20 data points for correlation analysis' + } + + # Extract sentiment scores + sentiments = [h['sentiment'] for h in self.sentiment_history] + + # Would fetch actual price data here + # For demonstration, simulating price correlation + + # Calculate correlation coefficient (simulated) + correlation = 0.65 # Simulated positive correlation + + # Calculate lead/lag relationship + # Does sentiment lead price, or vice versa? + sentiment_leads_price = True # Simulated + lag_hours = 6 # Simulated + + return { + 'status': 'success', + 'correlation_coefficient': correlation, + 'correlation_strength': ( + 'strong' if abs(correlation) > 0.7 else + 'moderate' if abs(correlation) > 0.4 else + 'weak' + ), + 'relationship': ( + 'Sentiment leads price' if sentiment_leads_price + else 'Price leads sentiment' + ), + 'lag_hours': lag_hours, + 'sample_size': len(sentiments), + 'interpretation': ( + 'Sentiment changes typically precede price movements by ~6 hours. ' + 'Strong correlation suggests sentiment is a useful leading indicator.' + if sentiment_leads_price and abs(correlation) > 0.6 + else 'Moderate correlation. Sentiment should be combined with other factors.' + ) + } + + except Exception as e: + logger.error(f"Correlation analysis error: {e}") + return { + 'status': 'error', + 'message': str(e) + } + + def _interpret_sentiment(self, score: float) -> str: + """Interpret aggregate sentiment score""" + if score > 0.6: + return 'Very Bullish' + elif score > 0.2: + return 'Bullish' + elif score > -0.2: + return 'Neutral' + elif score > -0.6: + return 'Bearish' + else: + return 'Very Bearish' + + def _empty_analysis_result(self, symbol: str) -> Dict: + """Return empty analysis result""" + return { + 'symbol': symbol, + 'timestamp': datetime.now().isoformat(), + 'error': 'Unable to gather sufficient sentiment data', + 'aggregate_sentiment': None, + 'fear_greed_index': None, + 'sentiment_sources': [], + 'alerts': [] + } + + async def close(self): + """Cleanup resources""" + await self.onchain_analyzer.close() + + +async def main(): + """Example usage""" + + # Configuration + config = { + 'twitter_bearer_token': 'YOUR_TWITTER_TOKEN', + 'reddit_client_id': 'YOUR_REDDIT_CLIENT_ID', + 'reddit_client_secret': 'YOUR_REDDIT_SECRET', + 'telegram_api_id': 'YOUR_TELEGRAM_API_ID', + 'telegram_api_hash': 'YOUR_TELEGRAM_HASH', + 'telegram_channels': ['bitcoin', 'cryptocurrency'], + 'whale_alert_api_key': 'YOUR_WHALE_ALERT_KEY', + 'ethereum_rpc': 'https://mainnet.infura.io/v3/YOUR_KEY' + } + + # Initialize aggregator + aggregator = MarketSentimentAggregator(config) + + try: + # Analyze sentiment + result = await aggregator.analyze_complete_sentiment('BTC') + + # Pretty print results + print("\n" + "="*80) + print(f"MARKET SENTIMENT ANALYSIS - {result['symbol']}") + print("="*80) + + print(f"\nFear & Greed Index: {result['fear_greed_index']['score']} - " + f"{result['fear_greed_index']['level']}") + print(f"Trend: {result['fear_greed_index']['trend']}") + print(f"Historical Percentile: {result['fear_greed_index']['historical_percentile']:.1f}%") + + print(f"\nAggregate Sentiment: {result['aggregate_sentiment']['interpretation']}") + print(f"Score: {result['aggregate_sentiment']['score']:.3f}") + + print("\nSentiment by Source:") + print("-" * 80) + for source in result['sentiment_sources']: + print(f" {source['source']:20} | Score: {source['score']:6.3f} | " + f"Confidence: {source['confidence']:.2f} | Samples: {source['sample_size']}") + + if result['alerts']: + print("\nAlerts:") + print("-" * 80) + for alert in result['alerts']: + print(f" [{alert['severity'].upper()}] {alert['message']}") + + print("\nData Quality:") + print("-" * 80) + print(f" Sources Analyzed: {result['data_quality']['sources_analyzed']}") + print(f" Total Samples: {result['data_quality']['total_samples']}") + print(f" Avg Confidence: {result['data_quality']['avg_confidence']:.2%}") + print(f" Coverage: {result['data_quality']['coverage_percentage']:.1f}%") + + if result['correlation_analysis']['status'] == 'success': + print("\nSentiment-Price Correlation:") + print("-" * 80) + corr = result['correlation_analysis'] + print(f" Correlation: {corr['correlation_coefficient']:.3f} ({corr['correlation_strength']})") + print(f" Relationship: {corr['relationship']}") + print(f" {corr['interpretation']}") + + print("\n" + "="*80) + + # Export to JSON + output_file = f"sentiment_analysis_{result['symbol']}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json" + with open(output_file, 'w') as f: + json.dump(result, f, indent=2) + print(f"\nFull analysis exported to: {output_file}") + + finally: + await aggregator.close() + + +if __name__ == '__main__': + asyncio.run(main()) +``` + +## Usage Examples + +### Command-Line Analysis +```bash +# Basic sentiment analysis +/analyze-sentiment BTC + +# Analysis with specific lookback period +/analyze-sentiment ETH --lookback 48h + +# Export to JSON +/analyze-sentiment BTC --output sentiment_btc.json + +# Monitor sentiment in real-time +/analyze-sentiment BTC --monitor --interval 1h +``` + +### Integration with Trading Strategy +```python +# Use sentiment for entry signals +result = await aggregator.analyze_complete_sentiment('BTC') + +if result['fear_greed_index']['level'] == 'Extreme Fear': + # Potential buying opportunity + if result['aggregate_sentiment']['score'] < -0.6: + print("STRONG BUY SIGNAL: Extreme fear with very bearish sentiment") + +# Check for reversal signals +if len(result['alerts']) > 0: + for alert in result['alerts']: + if alert['type'] == 'contrarian_buy_opportunity': + print(f"Contrarian signal: {alert['message']}") +``` + +## Interpretation Guidelines + +### Fear & Greed Extremes +- **Extreme Fear (0-25)**: Historically good buying opportunities (contrarian) +- **Extreme Greed (75-100)**: Potential market tops, consider profit-taking + +### Derivatives Signals +- **High positive funding rates**: Overleveraged longs, potential for long squeeze +- **High negative funding rates**: Overleveraged shorts, potential for short squeeze +- **Extreme put/call ratios**: Sentiment extremes often precede reversals + +### Whale Activity +- **Large exchange withdrawals**: Accumulation phase (bullish) +- **Large exchange deposits**: Distribution phase (bearish) +- **Divergence from retail**: Smart money positioning differently + +## Limitations & Considerations + +1. **Sentiment Lags**: Social sentiment often lags price action +2. **Manipulation**: Coordinated FUD/FOMO campaigns can skew results +3. **Bot Activity**: Social media bots can create artificial sentiment +4. **Sample Bias**: Limited to English-language sources +5. **Market Context**: Sentiment should be combined with technical and fundamental analysis + +## Advanced Features + +- **Sentiment momentum**: Rate of change in sentiment scores +- **Source divergence detection**: Conflicting signals across sources +- **Influencer tracking**: Weighted sentiment from high-follower accounts +- **Time decay**: Recent sentiment weighted more heavily +- **Volatility adjustment**: Sentiment normalized by market volatility + +## Future Enhancements + +- Machine learning sentiment prediction models +- Real-time sentiment streaming dashboard +- Integration with automated trading systems +- Multi-asset sentiment correlation +- Sentiment-based portfolio rebalancing \ No newline at end of file diff --git a/plugin.lock.json b/plugin.lock.json new file mode 100644 index 0000000..49bd960 --- /dev/null +++ b/plugin.lock.json @@ -0,0 +1,85 @@ +{ + "$schema": "internal://schemas/plugin.lock.v1.json", + "pluginId": "gh:jeremylongshore/claude-code-plugins-plus:plugins/crypto/market-sentiment-analyzer", + "normalized": { + "repo": null, + "ref": "refs/tags/v20251128.0", + "commit": "893f2f28d611534e11802af600c79b012005dbd0", + "treeHash": "7c38a594ec3f5ea2a277384a3afde198812ff0f21f717a696ced6f475c34455e", + "generatedAt": "2025-11-28T10:18:33.652089Z", + "toolVersion": "publish_plugins.py@0.2.0" + }, + "origin": { + "remote": "git@github.com:zhongweili/42plugin-data.git", + "branch": "master", + "commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390", + "repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data" + }, + "manifest": { + "name": "market-sentiment-analyzer", + "description": "Analyze market sentiment from social media, news, and on-chain data", + "version": "1.0.0" + }, + "content": { + "files": [ + { + "path": "README.md", + "sha256": "1588607524aa90b68b3d3a19e1aac1d62e39b888639027f3465c1dbafa98ca38" + }, + { + "path": ".claude-plugin/plugin.json", + "sha256": "0c50759636b3bd84322e6d719713bda2d753ecd2316acf3c30fdc9792196acf3" + }, + { + "path": "commands/analyze-sentiment.md", + "sha256": "b13096239c52f37fa2cd84865b00ae4fb94b7212284238590635a5bfac48c873" + }, + { + "path": "skills/skill-adapter/references/examples.md", + "sha256": "922bbc3c4ebf38b76f515b5c1998ebde6bf902233e00e2c5a0e9176f975a7572" + }, + { + "path": "skills/skill-adapter/references/best-practices.md", + "sha256": "c8f32b3566252f50daacd346d7045a1060c718ef5cfb07c55a0f2dec5f1fb39e" + }, + { + "path": "skills/skill-adapter/references/README.md", + "sha256": "0789c90e3a6c896458ec75710d9712cfa10339b890bee44554a9541fb8718a3f" + }, + { + "path": "skills/skill-adapter/scripts/helper-template.sh", + "sha256": "0881d5660a8a7045550d09ae0acc15642c24b70de6f08808120f47f86ccdf077" + }, + { + "path": "skills/skill-adapter/scripts/validation.sh", + "sha256": "92551a29a7f512d2036e4f1fb46c2a3dc6bff0f7dde4a9f699533e446db48502" + }, + { + "path": "skills/skill-adapter/scripts/README.md", + "sha256": "1f0b8ba2b81cf01bc2209ae96f93d5cd9cfe1b53a4665e19594ee47badf7a08c" + }, + { + "path": "skills/skill-adapter/assets/test-data.json", + "sha256": "ac17dca3d6e253a5f39f2a2f1b388e5146043756b05d9ce7ac53a0042eee139d" + }, + { + "path": "skills/skill-adapter/assets/README.md", + "sha256": "039b63fdb9a7a6eaa577454c0415e53746fb917e22b150ffe7528bd8c81bb0f1" + }, + { + "path": "skills/skill-adapter/assets/skill-schema.json", + "sha256": "f5639ba823a24c9ac4fb21444c0717b7aefde1a4993682897f5bf544f863c2cd" + }, + { + "path": "skills/skill-adapter/assets/config-template.json", + "sha256": "0c2ba33d2d3c5ccb266c0848fc43caa68a2aa6a80ff315d4b378352711f83e1c" + } + ], + "dirSha256": "7c38a594ec3f5ea2a277384a3afde198812ff0f21f717a696ced6f475c34455e" + }, + "security": { + "scannedAt": null, + "scannerVersion": null, + "flags": [] + } +} \ No newline at end of file diff --git a/skills/skill-adapter/assets/README.md b/skills/skill-adapter/assets/README.md new file mode 100644 index 0000000..8c45626 --- /dev/null +++ b/skills/skill-adapter/assets/README.md @@ -0,0 +1,7 @@ +# Assets + +Bundled resources for market-sentiment-analyzer skill + +- [ ] sentiment_report_template.md: Template for generating sentiment analysis reports. +- [ ] example_sentiment_data.json: Example JSON data for sentiment analysis results. +- [ ] visualization_templates/: Directory containing templates for visualizing sentiment data (e.g., charts, graphs). diff --git a/skills/skill-adapter/assets/config-template.json b/skills/skill-adapter/assets/config-template.json new file mode 100644 index 0000000..16f1712 --- /dev/null +++ b/skills/skill-adapter/assets/config-template.json @@ -0,0 +1,32 @@ +{ + "skill": { + "name": "skill-name", + "version": "1.0.0", + "enabled": true, + "settings": { + "verbose": false, + "autoActivate": true, + "toolRestrictions": true + } + }, + "triggers": { + "keywords": [ + "example-trigger-1", + "example-trigger-2" + ], + "patterns": [] + }, + "tools": { + "allowed": [ + "Read", + "Grep", + "Bash" + ], + "restricted": [] + }, + "metadata": { + "author": "Plugin Author", + "category": "general", + "tags": [] + } +} diff --git a/skills/skill-adapter/assets/skill-schema.json b/skills/skill-adapter/assets/skill-schema.json new file mode 100644 index 0000000..8dc154c --- /dev/null +++ b/skills/skill-adapter/assets/skill-schema.json @@ -0,0 +1,28 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Claude Skill Configuration", + "type": "object", + "required": ["name", "description"], + "properties": { + "name": { + "type": "string", + "pattern": "^[a-z0-9-]+$", + "maxLength": 64, + "description": "Skill identifier (lowercase, hyphens only)" + }, + "description": { + "type": "string", + "maxLength": 1024, + "description": "What the skill does and when to use it" + }, + "allowed-tools": { + "type": "string", + "description": "Comma-separated list of allowed tools" + }, + "version": { + "type": "string", + "pattern": "^\\d+\\.\\d+\\.\\d+$", + "description": "Semantic version (x.y.z)" + } + } +} diff --git a/skills/skill-adapter/assets/test-data.json b/skills/skill-adapter/assets/test-data.json new file mode 100644 index 0000000..f0cd871 --- /dev/null +++ b/skills/skill-adapter/assets/test-data.json @@ -0,0 +1,27 @@ +{ + "testCases": [ + { + "name": "Basic activation test", + "input": "trigger phrase example", + "expected": { + "activated": true, + "toolsUsed": ["Read", "Grep"], + "success": true + } + }, + { + "name": "Complex workflow test", + "input": "multi-step trigger example", + "expected": { + "activated": true, + "steps": 3, + "toolsUsed": ["Read", "Write", "Bash"], + "success": true + } + } + ], + "fixtures": { + "sampleInput": "example data", + "expectedOutput": "processed result" + } +} diff --git a/skills/skill-adapter/references/README.md b/skills/skill-adapter/references/README.md new file mode 100644 index 0000000..8c38d07 --- /dev/null +++ b/skills/skill-adapter/references/README.md @@ -0,0 +1,9 @@ +# References + +Bundled resources for market-sentiment-analyzer skill + +- [ ] sentiment_analysis_best_practices.md: Document outlining best practices for sentiment analysis in the crypto market. +- [ ] data_source_api_documentation.md: API documentation for data sources like Twitter, Reddit, CryptoQuant, Glassnode, etc. +- [ ] fear_greed_index_methodology.md: Detailed explanation of the Fear & Greed Index calculation methodology. +- [ ] sentiment_indicators_guide.md: Guide explaining the different sentiment indicators and their interpretation. +- [ ] market_sentiment_glossary.md: Glossary of terms related to market sentiment analysis. diff --git a/skills/skill-adapter/references/best-practices.md b/skills/skill-adapter/references/best-practices.md new file mode 100644 index 0000000..3505048 --- /dev/null +++ b/skills/skill-adapter/references/best-practices.md @@ -0,0 +1,69 @@ +# Skill Best Practices + +Guidelines for optimal skill usage and development. + +## For Users + +### Activation Best Practices + +1. **Use Clear Trigger Phrases** + - Match phrases from skill description + - Be specific about intent + - Provide necessary context + +2. **Provide Sufficient Context** + - Include relevant file paths + - Specify scope of analysis + - Mention any constraints + +3. **Understand Tool Permissions** + - Check allowed-tools in frontmatter + - Know what the skill can/cannot do + - Request appropriate actions + +### Workflow Optimization + +- Start with simple requests +- Build up to complex workflows +- Verify each step before proceeding +- Use skill consistently for related tasks + +## For Developers + +### Skill Development Guidelines + +1. **Clear Descriptions** + - Include explicit trigger phrases + - Document all capabilities + - Specify limitations + +2. **Proper Tool Permissions** + - Use minimal necessary tools + - Document security implications + - Test with restricted tools + +3. **Comprehensive Documentation** + - Provide usage examples + - Document common pitfalls + - Include troubleshooting guide + +### Maintenance + +- Keep version updated +- Test after tool updates +- Monitor user feedback +- Iterate on descriptions + +## Performance Tips + +- Scope skills to specific domains +- Avoid overlapping trigger phrases +- Keep descriptions under 1024 chars +- Test activation reliability + +## Security Considerations + +- Never include secrets in skill files +- Validate all inputs +- Use read-only tools when possible +- Document security requirements diff --git a/skills/skill-adapter/references/examples.md b/skills/skill-adapter/references/examples.md new file mode 100644 index 0000000..b1d8bd2 --- /dev/null +++ b/skills/skill-adapter/references/examples.md @@ -0,0 +1,70 @@ +# Skill Usage Examples + +This document provides practical examples of how to use this skill effectively. + +## Basic Usage + +### Example 1: Simple Activation + +**User Request:** +``` +[Describe trigger phrase here] +``` + +**Skill Response:** +1. Analyzes the request +2. Performs the required action +3. Returns results + +### Example 2: Complex Workflow + +**User Request:** +``` +[Describe complex scenario] +``` + +**Workflow:** +1. Step 1: Initial analysis +2. Step 2: Data processing +3. Step 3: Result generation +4. Step 4: Validation + +## Advanced Patterns + +### Pattern 1: Chaining Operations + +Combine this skill with other tools: +``` +Step 1: Use this skill for [purpose] +Step 2: Chain with [other tool] +Step 3: Finalize with [action] +``` + +### Pattern 2: Error Handling + +If issues occur: +- Check trigger phrase matches +- Verify context is available +- Review allowed-tools permissions + +## Tips & Best Practices + +- ✅ Be specific with trigger phrases +- ✅ Provide necessary context +- ✅ Check tool permissions match needs +- ❌ Avoid vague requests +- ❌ Don't mix unrelated tasks + +## Common Issues + +**Issue:** Skill doesn't activate +**Solution:** Use exact trigger phrases from description + +**Issue:** Unexpected results +**Solution:** Check input format and context + +## See Also + +- Main SKILL.md for full documentation +- scripts/ for automation helpers +- assets/ for configuration examples diff --git a/skills/skill-adapter/scripts/README.md b/skills/skill-adapter/scripts/README.md new file mode 100644 index 0000000..f2e9e36 --- /dev/null +++ b/skills/skill-adapter/scripts/README.md @@ -0,0 +1,10 @@ +# Scripts + +Bundled resources for market-sentiment-analyzer skill + +- [ ] analyze_sentiment.py: Script to perform sentiment analysis from various sources (social media, news, on-chain data). +- [ ] fetch_social_sentiment.py: Script to fetch social media sentiment data from Twitter, Reddit, etc. +- [ ] fetch_news_sentiment.py: Script to fetch news sentiment data from news aggregators. +- [ ] fetch_onchain_data.py: Script to fetch on-chain data from CryptoQuant, Glassnode, etc. +- [ ] calculate_fear_greed_index.py: Script to calculate the Fear & Greed Index. +- [ ] format_output.py: Script to format the sentiment analysis output in a user-friendly manner. diff --git a/skills/skill-adapter/scripts/helper-template.sh b/skills/skill-adapter/scripts/helper-template.sh new file mode 100755 index 0000000..c4aae90 --- /dev/null +++ b/skills/skill-adapter/scripts/helper-template.sh @@ -0,0 +1,42 @@ +#!/bin/bash +# Helper script template for skill automation +# Customize this for your skill's specific needs + +set -e + +function show_usage() { + echo "Usage: $0 [options]" + echo "" + echo "Options:" + echo " -h, --help Show this help message" + echo " -v, --verbose Enable verbose output" + echo "" +} + +# Parse arguments +VERBOSE=false + +while [[ $# -gt 0 ]]; do + case $1 in + -h|--help) + show_usage + exit 0 + ;; + -v|--verbose) + VERBOSE=true + shift + ;; + *) + echo "Unknown option: $1" + show_usage + exit 1 + ;; + esac +done + +# Your skill logic here +if [ "$VERBOSE" = true ]; then + echo "Running skill automation..." +fi + +echo "✅ Complete" diff --git a/skills/skill-adapter/scripts/validation.sh b/skills/skill-adapter/scripts/validation.sh new file mode 100755 index 0000000..590af58 --- /dev/null +++ b/skills/skill-adapter/scripts/validation.sh @@ -0,0 +1,32 @@ +#!/bin/bash +# Skill validation helper +# Validates skill activation and functionality + +set -e + +echo "🔍 Validating skill..." + +# Check if SKILL.md exists +if [ ! -f "../SKILL.md" ]; then + echo "❌ Error: SKILL.md not found" + exit 1 +fi + +# Validate frontmatter +if ! grep -q "^---$" "../SKILL.md"; then + echo "❌ Error: No frontmatter found" + exit 1 +fi + +# Check required fields +if ! grep -q "^name:" "../SKILL.md"; then + echo "❌ Error: Missing 'name' field" + exit 1 +fi + +if ! grep -q "^description:" "../SKILL.md"; then + echo "❌ Error: Missing 'description' field" + exit 1 +fi + +echo "✅ Skill validation passed"