Skip to main content
Glama
knishioka

IB Analytics MCP Server

by knishioka
technical.py7.54 kB
""" Technical sentiment analyzer Derives market sentiment from technical indicators including RSI, MACD, trend analysis, and support/resistance levels. """ from datetime import datetime, timedelta from decimal import Decimal import pandas as pd import yfinance as yf from ib_sec_mcp.analyzers.sentiment.base import BaseSentimentAnalyzer, SentimentScore class TechnicalSentimentAnalyzer(BaseSentimentAnalyzer): """ Analyze market sentiment from technical indicators Combines multiple technical analysis signals: - RSI (Relative Strength Index): Overbought/oversold conditions - MACD: Trend momentum and reversals - Trend: Short/medium/long term trend direction - Support/Resistance: Price action near key levels Sentiment Interpretation: - RSI < 30: Oversold (bullish reversal signal) - RSI 30-50: Bearish momentum - RSI 50-70: Bullish momentum - RSI > 70: Overbought (bearish reversal signal) - MACD > Signal: Bullish momentum - MACD < Signal: Bearish momentum """ def calculate_rsi(self, prices: "pd.Series[float]", period: int = 14) -> float: """Calculate RSI indicator""" delta = prices.diff() gain = delta.where(delta > 0, 0.0).rolling(window=period).mean() loss = (-delta).where(delta < 0, 0.0).rolling(window=period).mean() rs = gain / loss rsi = 100 - (100 / (1 + rs)) return float(rsi.iloc[-1]) def calculate_macd( self, prices: "pd.Series[float]", fast: int = 12, slow: int = 26, signal: int = 9 ) -> tuple[float, float]: """Calculate MACD and signal line""" ema_fast = prices.ewm(span=fast, adjust=False).mean() ema_slow = prices.ewm(span=slow, adjust=False).mean() macd = ema_fast - ema_slow macd_signal = macd.ewm(span=signal, adjust=False).mean() return float(macd.iloc[-1]), float(macd_signal.iloc[-1]) async def analyze_sentiment(self, symbol: str) -> SentimentScore: """ Analyze technical sentiment for a symbol Args: symbol: Stock ticker symbol Returns: SentimentScore with technical-based sentiment Raises: Exception: If technical data is unavailable """ try: # Fetch historical price data ticker = yf.Ticker(symbol) end_date = datetime.now() start_date = end_date - timedelta(days=365) # 1 year of data hist = ticker.history(start=start_date, end=end_date, interval="1d") if hist.empty or len(hist) < 50: raise ValueError(f"Insufficient price data for {symbol}") prices = hist["Close"] scores = [] themes = [] risk_factors = [] data_points = 0 # 1. RSI Sentiment try: rsi = self.calculate_rsi(prices) data_points += 1 if rsi < 30: rsi_score = Decimal("0.5") # Oversold (bullish) themes.append("oversold_rsi") elif rsi < 50: rsi_score = Decimal("-0.2") # Bearish momentum elif rsi < 70: rsi_score = Decimal("0.2") # Bullish momentum themes.append("bullish_momentum") else: rsi_score = Decimal("-0.5") # Overbought (bearish) risk_factors.append("overbought_rsi") scores.append(rsi_score) except (ValueError, ZeroDivisionError, IndexError): # Skip RSI if calculation fails (insufficient data or divide by zero) data_points -= 1 if data_points > 0 else 0 # 2. MACD Sentiment try: macd, macd_signal = self.calculate_macd(prices) data_points += 1 # MACD above signal = bullish if macd > macd_signal: macd_score = Decimal("0.3") themes.append("bullish_macd_cross") elif macd < macd_signal: macd_score = Decimal("-0.3") risk_factors.append("bearish_macd_cross") else: macd_score = Decimal("0.0") scores.append(macd_score) except (ValueError, IndexError): # Skip MACD if calculation fails (insufficient data) data_points -= 1 if data_points > 0 else 0 # 3. Trend Sentiment (Simple moving averages) try: sma_20 = prices.rolling(window=20).mean().iloc[-1] sma_50 = prices.rolling(window=50).mean().iloc[-1] current_price = prices.iloc[-1] data_points += 1 # Price above both SMAs = bullish if current_price > sma_20 and current_price > sma_50: if sma_20 > sma_50: trend_score = Decimal("0.4") # Strong uptrend themes.append("strong_uptrend") else: trend_score = Decimal("0.2") # Moderate uptrend # Price below both SMAs = bearish elif current_price < sma_20 and current_price < sma_50: if sma_20 < sma_50: trend_score = Decimal("-0.4") # Strong downtrend risk_factors.append("strong_downtrend") else: trend_score = Decimal("-0.2") # Moderate downtrend else: trend_score = Decimal("0.0") # Mixed/choppy scores.append(trend_score) except (ValueError, IndexError): # Skip trend if calculation fails (insufficient data) data_points -= 1 if data_points > 0 else 0 # Calculate composite score if not scores: # No technical data available return SentimentScore( score=Decimal("0.0"), confidence=Decimal("0.0"), timestamp=datetime.now(), key_themes=["no_technical_data"], risk_factors=[], reasoning=f"No technical data available for {symbol}", ) # Average all scores avg_score = sum(scores) / len(scores) # Confidence based on data point count # More data points = higher confidence confidence = min(Decimal(str(data_points)) / Decimal("3"), Decimal("1.0")) reasoning = ( f"Analyzed {data_points} technical indicators for {symbol}. " f"Sentiment based on RSI, MACD, and trend analysis." ) return SentimentScore( score=avg_score, confidence=confidence, timestamp=datetime.now(), key_themes=themes, risk_factors=risk_factors, reasoning=reasoning, ) except Exception as e: # Return neutral sentiment on error return SentimentScore( score=Decimal("0.0"), confidence=Decimal("0.0"), timestamp=datetime.now(), key_themes=[], risk_factors=["technical_analysis_error"], reasoning=f"Failed to analyze technical sentiment: {str(e)}", )

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/knishioka/ib-sec-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server