Whitepaper
Docs
Sign In
Tool
Tool
v0.0.9
Stock Reporter
Tool ID
stock_market_helper
Creator
@pyotrgrowpotkin
Downloads
6.3K+
A comprehensive stock analysis tool that gathers data from Finnhub Free API and compiles a detailed report.
Get
README
No README available
Tool Code
Show
""" title: Stock Market Helper description: A comprehensive stock analysis tool that gathers data from Finnhub API and compiles a detailed report. author: Pyotr Growpotkin author_url: https://github.com/christ-offer/ github: https://github.com/christ-offer/open-webui-tools funding_url: https://github.com/open-webui version: 0.0.9 license: MIT requirements: finnhub-python """ import finnhub import requests import aiohttp import asyncio from transformers import pipeline, AutoTokenizer, AutoModelForSequenceClassification import torch from bs4 import BeautifulSoup from pydantic import BaseModel from datetime import datetime, timedelta from typing import ( Dict, Any, List, Union, Generator, Iterator, Tuple, Optional, Callable, Awaitable, ) from functools import lru_cache def _format_date(date: datetime) -> str: """Helper function to format date for Finnhub API""" return date.strftime("%Y-%m-%d") # Caching for expensive operations @lru_cache(maxsize=128) def _get_sentiment_model(): model_name = "mrm8488/distilroberta-finetuned-financial-news-sentiment-analysis" tokenizer = AutoTokenizer.from_pretrained(model_name) model = AutoModelForSequenceClassification.from_pretrained(model_name) return tokenizer, model def _get_basic_info(client: finnhub.Client, ticker: str) -> Dict[str, Any]: """ Fetch comprehensive company information from Finnhub API. """ profile = client.company_profile2(symbol=ticker) basic_financials = client.company_basic_financials(ticker, "all") peers = client.company_peers(ticker) return {"profile": profile, "basic_financials": basic_financials, "peers": peers} def _get_current_price(client: finnhub.Client, ticker: str) -> Dict[str, float]: """ Fetch current price and daily change from Finnhub API. """ quote = client.quote(ticker) return { "current_price": quote["c"], "change": quote["dp"], "change_amount": quote["d"], "high": quote["h"], "low": quote["l"], "open": quote["o"], "previous_close": quote["pc"], } def _get_company_news(client: finnhub.Client, ticker: str) -> List[Dict[str, str]]: """ Fetch recent news articles about the company from Finnhub API. Returns a list of dictionaries containing news item details. """ end_date = datetime.now() start_date = end_date - timedelta(days=7) news = client.company_news(ticker, _format_date(start_date), _format_date(end_date)) news_items = news[:10] # Get the first 10 news items return [{"url": item["url"], "title": item["headline"]} for item in news_items] async def _async_web_scrape(session: aiohttp.ClientSession, url: str) -> str: """ Scrape and process a web page using r.jina.ai :param session: The aiohttp ClientSession to use for the request. :param url: The URL of the web page to scrape. :return: The scraped and processed content without the Links/Buttons section, or an error message. """ jina_url = f"https://r.jina.ai/{url}" headers = { "X-No-Cache": "true", "X-With-Images-Summary": "true", "X-With-Links-Summary": "true", } try: async with session.get(jina_url, headers=headers) as response: response.raise_for_status() content = await response.text() # Extract content and remove Links/Buttons section as its too many tokens links_section_start = content.rfind("Images:") if links_section_start != -1: content = content[:links_section_start].strip() return content except aiohttp.ClientError as e: return f"Error scraping web page: {str(e)}" # Asynchronous sentiment analysis async def _async_sentiment_analysis(content: str) -> Dict[str, Union[str, float]]: tokenizer, model = _get_sentiment_model() inputs = tokenizer(content, return_tensors="pt", truncation=True, max_length=512) with torch.no_grad(): outputs = model(**inputs) probabilities = torch.nn.functional.softmax(outputs.logits, dim=-1) sentiment_scores = probabilities.tolist()[0] # Update sentiment labels to match the new model's output sentiments = ["Neutral", "Positive", "Negative"] sentiment = sentiments[sentiment_scores.index(max(sentiment_scores))] confidence = max(sentiment_scores) return {"sentiment": sentiment, "confidence": confidence} # Asynchronous data gathering async def _async_gather_stock_data( client: finnhub.Client, ticker: str ) -> Dict[str, Any]: basic_info = _get_basic_info(client, ticker) current_price = _get_current_price(client, ticker) news_items = _get_company_news(client, ticker) async with aiohttp.ClientSession() as session: scrape_tasks = [_async_web_scrape(session, item["url"]) for item in news_items] contents = await asyncio.gather(*scrape_tasks) sentiment_tasks = [ _async_sentiment_analysis(content) for content in contents if content ] sentiments = await asyncio.gather(*sentiment_tasks) sentiment_results = [ { "url": news_items[i]["url"], "title": news_items[i]["title"], # "content": contents[i][:500] + "..." if contents[i] and len(contents[i]) > 500 else contents[i], "sentiment": sentiment["sentiment"], "confidence": sentiment["confidence"], } for i, sentiment in enumerate(sentiments) if contents[i] ] return { "basic_info": basic_info, "current_price": current_price, "sentiments": sentiment_results, } def _compile_report(data: Dict[str, Any]) -> str: """ Compile gathered data into a comprehensive structured report. """ profile = data["basic_info"]["profile"] financials = data["basic_info"]["basic_financials"] metrics = financials["metric"] peers = data["basic_info"]["peers"] price_data = data["current_price"] report = f""" Comprehensive Stock Analysis Report for {profile['name']} ({profile['ticker']}) Basic Information: Industry: {profile.get('finnhubIndustry', 'N/A')} Market Cap: {profile.get('marketCapitalization', 'N/A'):,.0f} M USD Share Outstanding: {profile.get('shareOutstanding', 'N/A'):,.0f} M Country: {profile.get('country', 'N/A')} Exchange: {profile.get('exchange', 'N/A')} IPO Date: {profile.get('ipo', 'N/A')} Current Trading Information: Current Price: ${price_data['current_price']:.2f} Daily Change: {price_data['change']:.2f}% (${price_data['change_amount']:.2f}) Day's Range: ${price_data['low']:.2f} - ${price_data['high']:.2f} Open: ${price_data['open']:.2f} Previous Close: ${price_data['previous_close']:.2f} Key Financial Metrics: 52 Week High: ${financials['metric'].get('52WeekHigh', 'N/A')} 52 Week Low: ${financials['metric'].get('52WeekLow', 'N/A')} P/E Ratio: {financials['metric'].get('peBasicExclExtraTTM', 'N/A')} EPS (TTM): ${financials['metric'].get('epsBasicExclExtraItemsTTM', 'N/A')} Return on Equity: {financials['metric'].get('roeRfy', 'N/A')}% Debt to Equity: {financials['metric'].get('totalDebtToEquityQuarterly', 'N/A')} Current Ratio: {financials['metric'].get('currentRatioQuarterly', 'N/A')} Dividend Yield: {financials['metric'].get('dividendYieldIndicatedAnnual', 'N/A')}% Peer Companies: {', '.join(peers[:5])} Detailed Financial Analysis: 1. Valuation Metrics: P/E Ratio: {metrics.get('peBasicExclExtraTTM', 'N/A')} - Interpretation: {'High (may be overvalued)' if metrics.get('peBasicExclExtraTTM', 0) > 25 else 'Moderate' if 15 <= metrics.get('peBasicExclExtraTTM', 0) <= 25 else 'Low (may be undervalued)'} P/B Ratio: {metrics.get('pbQuarterly', 'N/A')} - Interpretation: {'High' if metrics.get('pbQuarterly', 0) > 3 else 'Moderate' if 1 <= metrics.get('pbQuarterly', 0) <= 3 else 'Low'} 2. Profitability Metrics: Return on Equity: {metrics.get('roeRfy', 'N/A')}% - Interpretation: {'Excellent' if metrics.get('roeRfy', 0) > 20 else 'Good' if 15 <= metrics.get('roeRfy', 0) <= 20 else 'Average' if 10 <= metrics.get('roeRfy', 0) < 15 else 'Poor'} Net Profit Margin: {metrics.get('netProfitMarginTTM', 'N/A')}% - Interpretation: {'Excellent' if metrics.get('netProfitMarginTTM', 0) > 20 else 'Good' if 10 <= metrics.get('netProfitMarginTTM', 0) <= 20 else 'Average' if 5 <= metrics.get('netProfitMarginTTM', 0) < 10 else 'Poor'} 3. Liquidity and Solvency: Current Ratio: {metrics.get('currentRatioQuarterly', 'N/A')} - Interpretation: {'Strong' if metrics.get('currentRatioQuarterly', 0) > 2 else 'Healthy' if 1.5 <= metrics.get('currentRatioQuarterly', 0) <= 2 else 'Adequate' if 1 <= metrics.get('currentRatioQuarterly', 0) < 1.5 else 'Poor'} Debt-to-Equity Ratio: {metrics.get('totalDebtToEquityQuarterly', 'N/A')} - Interpretation: {'Low leverage' if metrics.get('totalDebtToEquityQuarterly', 0) < 0.5 else 'Moderate leverage' if 0.5 <= metrics.get('totalDebtToEquityQuarterly', 0) <= 1 else 'High leverage'} 4. Dividend Analysis: Dividend Yield: {metrics.get('dividendYieldIndicatedAnnual', 'N/A')}% - Interpretation: {'High yield' if metrics.get('dividendYieldIndicatedAnnual', 0) > 4 else 'Moderate yield' if 2 <= metrics.get('dividendYieldIndicatedAnnual', 0) <= 4 else 'Low yield'} 5. Market Performance: 52-Week Range: ${metrics.get('52WeekLow', 'N/A')} - ${metrics.get('52WeekHigh', 'N/A')} Current Price Position: {((price_data['current_price'] - metrics.get('52WeekLow', price_data['current_price'])) / (metrics.get('52WeekHigh', price_data['current_price']) - metrics.get('52WeekLow', price_data['current_price'])) * 100):.2f}% of 52-Week Range Beta: {metrics.get('beta', 'N/A')} - Interpretation: {'More volatile than market' if metrics.get('beta', 1) > 1 else 'Less volatile than market' if metrics.get('beta', 1) < 1 else 'Same volatility as market'} Overall Analysis: {profile['name']} shows {'strong' if metrics.get('roeRfy', 0) > 15 and metrics.get('currentRatioQuarterly', 0) > 1.5 else 'moderate' if metrics.get('roeRfy', 0) > 10 and metrics.get('currentRatioQuarterly', 0) > 1 else 'weak'} financial health with {'high' if metrics.get('peBasicExclExtraTTM', 0) > 25 else 'moderate' if 15 <= metrics.get('peBasicExclExtraTTM', 0) <= 25 else 'low'} valuation metrics. The company's profitability is {'excellent' if metrics.get('netProfitMarginTTM', 0) > 20 else 'good' if metrics.get('netProfitMarginTTM', 0) > 10 else 'average' if metrics.get('netProfitMarginTTM', 0) > 5 else 'poor'}, and it has {'low' if metrics.get('totalDebtToEquityQuarterly', 0) < 0.5 else 'moderate' if metrics.get('totalDebtToEquityQuarterly', 0) < 1 else 'high'} financial leverage. Investors should consider these factors along with their investment goals and risk tolerance. Recent News and Sentiment Analysis: """ for item in data["sentiments"]: report += f""" Title: {item['title']} URL: {item['url']} Sentiment Analysis: {item['sentiment']} (Confidence: {item['confidence']:.2f}) """ # Content Preview: {item['content'][:500]}... return report class Tools: class UserValves(BaseModel): FINNHUB_API_KEY: str = "" pass def __init__(self): pass async def compile_stock_report( self, ticker: str, __user__: dict = {}, __event_emitter__: Callable[[Any], Awaitable[None]] = None, ) -> str: """ Perform a comprehensive stock analysis and compile a detailed report for a given ticker using Finnhub's API. This function gathers various data points including: - Basic company information (industry, market cap, etc.) - Current trading information (price, daily change, etc.) - Key financial metrics (P/E ratio, EPS, ROE, etc.) - List of peer companies - Recent news articles with sentiment analysis using FinBERT The gathered data is then compiled into a structured, easy-to-read report. :param ticker: The stock ticker symbol (e.g., "AAPL" for Apple Inc.). :return: A comprehensive analysis report of the stock as a formatted string. """ await __event_emitter__( { "type": "status", "data": {"description": "Initializing client", "done": False}, } ) self.client = finnhub.Client(api_key=__user__["valves"].FINNHUB_API_KEY) await __event_emitter__( { "type": "status", "data": {"description": "Retrieving stock data", "done": False}, } ) data = await _async_gather_stock_data(self.client, ticker) await __event_emitter__( { "type": "status", "data": {"description": "Compiling stock report", "done": False}, } ) report = _compile_report(data) # Get lastest price from data last_price = data["current_price"]["current_price"] await __event_emitter__( { "type": "status", "data": { "description": "Finished creating report - latest price: " + str(last_price), "done": True, }, } ) return report