diff --git a/Stock-Price-Tracker/README.md b/Stock-Price-Tracker/README.md
new file mode 100644
index 0000000..b946f2c
--- /dev/null
+++ b/Stock-Price-Tracker/README.md
@@ -0,0 +1,298 @@
+# Stock Price Tracker š
+
+A comprehensive Python application that tracks stock prices using Yahoo Finance API, visualizes daily/weekly trends with interactive charts, and sends automated Telegram notifications for price movements and alerts.
+
+
+
+
+
+## ⨠Features
+
+- š **Real-time Stock Data**: Fetch live stock prices and historical data using Yahoo Finance API
+- š **Daily & Weekly Trends**: Visualize price movements over different timeframes
+- šØ **Multiple Chart Types**: Line charts, candlestick charts, volume analysis, and summary reports
+- š± **Telegram Alerts**: Receive instant notifications for price changes and threshold alerts
+- šÆ **Price Thresholds**: Set custom price alerts and get notified when triggered
+- š **Technical Analysis**: Moving averages, daily returns, and price distribution analysis
+- š¾ **Export Functionality**: Save charts as PNG/HTML files for reporting
+- š„ļø **Command-line Interface**: Easy-to-use CLI with multiple options
+
+## š Requirements
+
+- Python 3.8 or higher
+- Internet connection (for fetching stock data)
+- Telegram Bot (optional, for alerts)
+
+## š Installation
+
+1. **Clone or download this project**
+ ```bash
+ cd Stock-Price-Tracker
+ ```
+
+2. **Install required dependencies**
+ ```bash
+ pip install -r requirements.txt
+ ```
+
+3. **Set up Telegram Bot (Optional but recommended)**
+
+ To receive price alerts via Telegram:
+
+ - **Create a Telegram Bot:**
+ 1. Open Telegram and search for `@BotFather`
+ 2. Send `/newbot` command
+ 3. Follow instructions to create your bot
+ 4. Copy the **Bot Token** provided
+
+ - **Get your Chat ID:**
+ 1. Start a chat with your new bot
+ 2. Search for `@userinfobot` on Telegram
+ 3. Start a chat and it will show your **Chat ID**
+
+ - **Configure environment variables:**
+ ```bash
+ # Copy the example file
+ cp config/.env.example config/.env
+
+ # Edit config/.env and add your credentials
+ TELEGRAM_BOT_TOKEN=your_bot_token_here
+ TELEGRAM_CHAT_ID=your_chat_id_here
+ ```
+
+## š Usage
+
+### Basic Commands
+
+**Track daily prices:**
+```bash
+python stock_tracker.py AAPL --daily
+```
+
+**Track weekly prices:**
+```bash
+python stock_tracker.py GOOGL --weekly
+```
+
+**Get stock information:**
+```bash
+python stock_tracker.py TSLA --info
+```
+
+### Advanced Usage
+
+**Track with custom timeframe:**
+```bash
+# 60 days of daily data
+python stock_tracker.py MSFT --daily --days 60
+
+# 24 weeks of weekly data
+python stock_tracker.py AMZN --weekly --weeks 24
+```
+
+**Save charts without displaying:**
+```bash
+python stock_tracker.py AAPL --daily --save-plots --no-plot
+```
+
+**Enable Telegram alerts:**
+```bash
+# Track with alert notification
+python stock_tracker.py TSLA --daily --telegram --alert
+
+# Check price threshold and alert
+python stock_tracker.py AAPL --threshold 150 --threshold-type above --telegram
+```
+
+**Price threshold monitoring:**
+```bash
+# Alert when price goes above $200
+python stock_tracker.py GOOGL --threshold 200 --threshold-type above --telegram
+
+# Alert when price goes below $100
+python stock_tracker.py MSFT --threshold 100 --threshold-type below --telegram
+```
+
+### Command-line Arguments
+
+| Argument | Description | Default |
+|----------|-------------|---------|
+| `ticker` | Stock ticker symbol (required) | - |
+| `--daily` | Track daily prices | False |
+| `--weekly` | Track weekly prices | False |
+| `--days` | Number of days to track | 30 |
+| `--weeks` | Number of weeks to track | 12 |
+| `--plot` | Display plots | True |
+| `--no-plot` | Don't display plots | False |
+| `--save-plots` | Save plots to files | False |
+| `--telegram` | Enable Telegram alerts | False |
+| `--alert` | Send Telegram alert | False |
+| `--threshold` | Price threshold for alerts | None |
+| `--threshold-type` | Threshold type (above/below) | above |
+| `--info` | Display stock info only | False |
+
+## š Example Output
+
+### Console Output
+```
+============================================================
+š STOCK PRICE TRACKER
+============================================================
+š Validating ticker symbol: AAPL
+ā
Ticker AAPL is valid
+
+============================================================
+š STOCK INFORMATION: AAPL
+============================================================
+Company: Apple Inc.
+Sector: Technology
+Industry: Consumer Electronics
+Current Price: $178.45
+Previous Close: $175.20
+52W High: $198.23
+52W Low: $124.17
+Market Cap: $2850.00B
+============================================================
+
+š Fetching 30 days of data for AAPL...
+ā
Retrieved 30 days of data
+
+āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
+š DAILY STATISTICS
+āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
+Start Price: $165.30
+End Price: $178.45
+Change: $13.15 (7.95%)
+Highest: $180.50
+Lowest: $164.90
+āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
+```
+
+### Telegram Alert Examples
+
+**Daily Summary Alert:**
+```
+š DAILY SUMMARY: AAPL
+
+š Overall: UP 7.95%
+
+š° Opening: $165.30
+šµ Closing: $178.45
+š¼ High: $180.50
+š½ Low: $164.90
+š Change: $13.15 (+7.95%)
+
+š
Date: 2025-12-08 10:30:45
+```
+
+**Threshold Alert:**
+```
+š THRESHOLD ALERT: AAPL
+
+āļø Price is now above $175.00!
+
+š° Current Price: $178.45
+šÆ Threshold: $175.00
+
+š
Time: 2025-12-08 10:30:45
+```
+
+## š Project Structure
+
+```
+Stock-Price-Tracker/
+āāā stock_tracker.py # Main application
+āāā utils/
+ā āāā __init__.py
+ā āāā yahoo_finance.py # Yahoo Finance API integration
+ā āāā plotter.py # Chart generation & visualization
+ā āāā telegram_alert.py # Telegram bot integration
+āāā config/
+ā āāā .env.example # Environment variables template
+āāā assets/
+ā āāā img/ # Generated charts and screenshots
+āāā requirements.txt # Python dependencies
+āāā README.md # Documentation
+```
+
+## š ļø Tech Stack
+
+| Technology | Purpose |
+|------------|---------|
+| **yfinance** | Yahoo Finance API for stock data |
+| **pandas** | Data manipulation and analysis |
+| **matplotlib** | Static chart generation |
+| **plotly** | Interactive candlestick charts |
+| **python-telegram-bot** | Telegram bot integration |
+| **python-dotenv** | Environment variable management |
+
+## š Visualization Features
+
+1. **Price Trend Chart**
+ - Line chart with closing prices
+ - 7-day and 30-day moving averages
+ - Clean, professional styling
+
+2. **Candlestick Chart**
+ - Interactive OHLC (Open, High, Low, Close) visualization
+ - Volume bar chart
+ - Zoom and pan capabilities
+
+3. **Volume Analysis**
+ - Price vs. volume correlation
+ - Average volume indicators
+ - Color-coded volume bars
+
+4. **Summary Report**
+ - Comprehensive multi-panel view
+ - Price statistics summary
+ - Daily range visualization
+
+## š Telegram Alerts
+
+The app sends various types of alerts:
+
+- **š Price Updates**: Regular stock price summaries
+- **šØ Threshold Alerts**: Notifications when price crosses set levels
+- **š Daily Summaries**: End-of-day price reports with statistics
+- **ā Error Alerts**: Notifications for any issues or errors
+
+## šÆ Use Cases
+
+- **Day Traders**: Monitor intraday price movements with alerts
+- **Long-term Investors**: Track weekly trends and major price changes
+- **Portfolio Monitoring**: Set threshold alerts for multiple stocks
+- **Market Research**: Analyze historical trends and patterns
+- **Automated Reporting**: Generate and save charts for presentations
+
+## š Supported Stock Symbols
+
+Any valid ticker symbol from Yahoo Finance, including:
+- **US Stocks**: AAPL, GOOGL, MSFT, TSLA, AMZN, etc.
+- **International**: Use country suffix (e.g., RELIANCE.NS for India)
+- **ETFs**: SPY, QQQ, VTI, etc.
+- **Cryptocurrencies**: BTC-USD, ETH-USD, etc.
+
+## š¤ Contributing
+
+Contributions are welcome! Feel free to:
+- Report bugs or issues
+- Suggest new features
+- Improve documentation
+- Submit pull requests
+
+## š License
+
+This project is open source and available for educational purposes.
+
+## ā ļø Disclaimer
+
+This tool is for informational and educational purposes only. It should not be considered as financial advice. Always do your own research before making investment decisions.
+
+## šØāš» Author
+
+Created as part of the [Python-Projects](https://github.com/Grow-with-Open-Source/Python-Projects) repository.
+
+---
+
+**Happy Trading! šš°**
diff --git a/Stock-Price-Tracker/config/.env.example b/Stock-Price-Tracker/config/.env.example
new file mode 100644
index 0000000..efcae89
--- /dev/null
+++ b/Stock-Price-Tracker/config/.env.example
@@ -0,0 +1,9 @@
+# Telegram Bot Configuration
+# Get your bot token from @BotFather on Telegram
+TELEGRAM_BOT_TOKEN=your_bot_token_here
+
+# Get your chat ID from @userinfobot on Telegram
+TELEGRAM_CHAT_ID=your_chat_id_here
+
+# Optional: Set price alert thresholds (percentage)
+PRICE_ALERT_THRESHOLD=5.0
diff --git a/Stock-Price-Tracker/requirements.txt b/Stock-Price-Tracker/requirements.txt
new file mode 100644
index 0000000..d32a0fd
--- /dev/null
+++ b/Stock-Price-Tracker/requirements.txt
@@ -0,0 +1,7 @@
+yfinance>=0.2.32
+pandas>=2.0.0
+matplotlib>=3.7.0
+plotly>=5.17.0
+python-telegram-bot>=20.0
+python-dotenv>=1.0.0
+kaleido>=0.2.1
diff --git a/Stock-Price-Tracker/stock_tracker.py b/Stock-Price-Tracker/stock_tracker.py
new file mode 100644
index 0000000..357a8b1
--- /dev/null
+++ b/Stock-Price-Tracker/stock_tracker.py
@@ -0,0 +1,347 @@
+#!/usr/bin/env python3
+"""
+Stock Price Tracker
+Track stock prices, visualize trends, and send Telegram alerts
+
+Author: Python-Projects Contributors
+"""
+
+import os
+import sys
+import argparse
+from datetime import datetime
+from pathlib import Path
+from dotenv import load_dotenv
+
+# Add utils to path
+sys.path.insert(0, str(Path(__file__).parent))
+
+from utils.yahoo_finance import StockDataFetcher
+from utils.plotter import StockPlotter
+from utils.telegram_alert import TelegramAlert
+
+
+class StockTracker:
+ """Main Stock Price Tracker Application"""
+
+ def __init__(self, ticker: str, use_telegram: bool = False):
+ """
+ Initialize Stock Tracker
+
+ Args:
+ ticker (str): Stock ticker symbol
+ use_telegram (bool): Enable Telegram alerts
+ """
+ self.ticker = ticker.upper()
+ self.fetcher = StockDataFetcher(self.ticker)
+ self.plotter = StockPlotter(self.ticker)
+ self.telegram = TelegramAlert() if use_telegram else None
+
+ def validate_ticker(self) -> bool:
+ """Validate if ticker symbol is valid"""
+ print(f"š Validating ticker symbol: {self.ticker}")
+ if self.fetcher.is_valid_ticker():
+ print(f"ā
Ticker {self.ticker} is valid")
+ return True
+ else:
+ print(f"ā Invalid ticker symbol: {self.ticker}")
+ return False
+
+ def display_stock_info(self):
+ """Display basic stock information"""
+ print(f"\n{'='*60}")
+ print(f"š STOCK INFORMATION: {self.ticker}")
+ print(f"{'='*60}")
+
+ info = self.fetcher.get_stock_info()
+ if info:
+ print(f"Company: {info.get('name', 'N/A')}")
+ print(f"Sector: {info.get('sector', 'N/A')}")
+ print(f"Industry: {info.get('industry', 'N/A')}")
+ print(f"Current Price: ${info.get('current_price', 'N/A')}")
+ print(f"Previous Close: ${info.get('previous_close', 'N/A')}")
+ print(f"52W High: ${info.get('52_week_high', 'N/A')}")
+ print(f"52W Low: ${info.get('52_week_low', 'N/A')}")
+
+ if isinstance(info.get('market_cap'), (int, float)):
+ market_cap_b = info['market_cap'] / 1e9
+ print(f"Market Cap: ${market_cap_b:.2f}B")
+ print(f"{'='*60}\n")
+ else:
+ print("ā Unable to fetch stock information\n")
+
+ def track_daily(self, days: int = 30, plot: bool = True,
+ save_plots: bool = False, send_alert: bool = False):
+ """
+ Track daily stock prices
+
+ Args:
+ days (int): Number of days to track
+ plot (bool): Display plots
+ save_plots (bool): Save plots to files
+ send_alert (bool): Send Telegram alert
+ """
+ print(f"\nš Fetching {days} days of data for {self.ticker}...")
+ data = self.fetcher.get_daily_data(days)
+
+ if data.empty:
+ print("ā No data available")
+ return
+
+ print(f"ā
Retrieved {len(data)} days of data")
+
+ # Calculate statistics
+ stats = self.fetcher.calculate_price_change(data)
+
+ # Display statistics
+ self._display_statistics(stats, "Daily")
+
+ # Create plots
+ if plot or save_plots:
+ self._create_plots(data, stats, "daily", plot, save_plots)
+
+ # Send Telegram alert
+ if send_alert and self.telegram and self.telegram.is_configured():
+ print("\nš± Sending Telegram alert...")
+ success = self.telegram.send_stock_alert(self.ticker, stats, "daily_summary")
+ if success:
+ print("ā
Alert sent successfully")
+ else:
+ print("ā Failed to send alert")
+
+ def track_weekly(self, weeks: int = 12, plot: bool = True,
+ save_plots: bool = False, send_alert: bool = False):
+ """
+ Track weekly stock prices
+
+ Args:
+ weeks (int): Number of weeks to track
+ plot (bool): Display plots
+ save_plots (bool): Save plots to files
+ send_alert (bool): Send Telegram alert
+ """
+ print(f"\nš Fetching {weeks} weeks of data for {self.ticker}...")
+ data = self.fetcher.get_weekly_data(weeks)
+
+ if data.empty:
+ print("ā No data available")
+ return
+
+ print(f"ā
Retrieved {len(data)} weeks of data")
+
+ # Calculate statistics
+ stats = self.fetcher.calculate_price_change(data)
+
+ # Display statistics
+ self._display_statistics(stats, "Weekly")
+
+ # Create plots
+ if plot or save_plots:
+ self._create_plots(data, stats, "weekly", plot, save_plots)
+
+ # Send Telegram alert
+ if send_alert and self.telegram and self.telegram.is_configured():
+ print("\nš± Sending Telegram alert...")
+ success = self.telegram.send_stock_alert(self.ticker, stats, "update")
+ if success:
+ print("ā
Alert sent successfully")
+ else:
+ print("ā Failed to send alert")
+
+ def check_price_threshold(self, threshold: float, threshold_type: str = "above"):
+ """
+ Check if current price crosses a threshold
+
+ Args:
+ threshold (float): Price threshold
+ threshold_type (str): 'above' or 'below'
+ """
+ print(f"\nšÆ Checking price threshold...")
+ current_price = self.fetcher.get_current_price()
+
+ if current_price is None:
+ print("ā Unable to fetch current price")
+ return
+
+ print(f"Current Price: ${current_price:.2f}")
+ print(f"Threshold: ${threshold:.2f} ({threshold_type})")
+
+ triggered = False
+ if threshold_type == "above" and current_price > threshold:
+ triggered = True
+ print(f"ā
Price is above threshold!")
+ elif threshold_type == "below" and current_price < threshold:
+ triggered = True
+ print(f"ā
Price is below threshold!")
+ else:
+ print(f"ā¹ļø Threshold not triggered")
+
+ if triggered and self.telegram and self.telegram.is_configured():
+ print("\nš± Sending threshold alert...")
+ success = self.telegram.send_price_threshold_alert(
+ self.ticker, current_price, threshold, threshold_type
+ )
+ if success:
+ print("ā
Alert sent successfully")
+
+ def _display_statistics(self, stats: dict, timeframe: str):
+ """Display price change statistics"""
+ print(f"\n{'ā'*60}")
+ print(f"š {timeframe.upper()} STATISTICS")
+ print(f"{'ā'*60}")
+ print(f"Start Price: ${stats.get('start_price', 'N/A')}")
+ print(f"End Price: ${stats.get('end_price', 'N/A')}")
+ print(f"Change: ${stats.get('change', 'N/A')} ({stats.get('change_percent', 'N/A')}%)")
+ print(f"Highest: ${stats.get('highest', 'N/A')}")
+ print(f"Lowest: ${stats.get('lowest', 'N/A')}")
+ print(f"{'ā'*60}\n")
+
+ def _create_plots(self, data, stats, timeframe: str,
+ show: bool, save: bool):
+ """Create and save plots"""
+ assets_dir = Path(__file__).parent / "assets" / "img"
+ assets_dir.mkdir(parents=True, exist_ok=True)
+
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
+
+ # Price trend plot
+ print(f"š Creating price trend plot...")
+ save_path = None
+ if save:
+ save_path = str(assets_dir / f"{self.ticker}_{timeframe}_trend_{timestamp}.png")
+
+ self.plotter.plot_price_trend(
+ data,
+ title=f"{self.ticker} {timeframe.capitalize()} Price Trend",
+ save_path=save_path,
+ show_plot=show
+ )
+
+ # Candlestick chart (only for daily data with OHLC)
+ if timeframe == "daily" and len(data) > 0:
+ print(f"š Creating candlestick chart...")
+ save_path = None
+ if save:
+ save_path = str(assets_dir / f"{self.ticker}_candlestick_{timestamp}.html")
+
+ self.plotter.plot_candlestick(
+ data,
+ save_path=save_path,
+ show_plot=show
+ )
+
+ # Summary report
+ print(f"š Creating summary report...")
+ save_path = None
+ if save:
+ save_path = str(assets_dir / f"{self.ticker}_summary_{timestamp}.png")
+
+ self.plotter.create_summary_report(
+ data, stats,
+ save_path=save_path,
+ show_plot=show
+ )
+
+
+def main():
+ """Main function"""
+ parser = argparse.ArgumentParser(
+ description="Stock Price Tracker - Track stocks, visualize trends, send alerts",
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ epilog="""
+Examples:
+ # Track daily prices for Apple
+ python stock_tracker.py AAPL --daily
+
+ # Track weekly prices for Google with plots
+ python stock_tracker.py GOOGL --weekly --plot
+
+ # Track with Telegram alerts
+ python stock_tracker.py TSLA --daily --telegram --alert
+
+ # Save plots without displaying
+ python stock_tracker.py MSFT --daily --save-plots --no-plot
+
+ # Check price threshold
+ python stock_tracker.py AAPL --threshold 150 --threshold-type above --telegram
+ """
+ )
+
+ parser.add_argument('ticker', type=str, help='Stock ticker symbol (e.g., AAPL, GOOGL, TSLA)')
+ parser.add_argument('--daily', action='store_true', help='Track daily prices')
+ parser.add_argument('--weekly', action='store_true', help='Track weekly prices')
+ parser.add_argument('--days', type=int, default=30, help='Number of days to track (default: 30)')
+ parser.add_argument('--weeks', type=int, default=12, help='Number of weeks to track (default: 12)')
+ parser.add_argument('--plot', action='store_true', default=True, help='Display plots (default: True)')
+ parser.add_argument('--no-plot', action='store_true', help='Do not display plots')
+ parser.add_argument('--save-plots', action='store_true', help='Save plots to files')
+ parser.add_argument('--telegram', action='store_true', help='Enable Telegram alerts')
+ parser.add_argument('--alert', action='store_true', help='Send Telegram alert')
+ parser.add_argument('--threshold', type=float, help='Price threshold for alerts')
+ parser.add_argument('--threshold-type', choices=['above', 'below'], default='above',
+ help='Threshold type (default: above)')
+ parser.add_argument('--info', action='store_true', help='Display stock information only')
+
+ args = parser.parse_args()
+
+ # Load environment variables
+ env_path = Path(__file__).parent / 'config' / '.env'
+ if env_path.exists():
+ load_dotenv(env_path)
+
+ # Display header
+ print("\n" + "="*60)
+ print("š STOCK PRICE TRACKER")
+ print("="*60)
+
+ # Initialize tracker
+ tracker = StockTracker(args.ticker, use_telegram=args.telegram)
+
+ # Validate ticker
+ if not tracker.validate_ticker():
+ sys.exit(1)
+
+ # Test Telegram connection if enabled
+ if args.telegram:
+ print("\nš± Testing Telegram connection...")
+ if tracker.telegram and tracker.telegram.test_connection():
+ tracker.telegram.send_welcome_message(args.ticker)
+ else:
+ print("ā ļø Telegram not configured properly. Continuing without alerts.")
+
+ # Display stock info
+ if args.info:
+ tracker.display_stock_info()
+ return
+
+ # Check threshold
+ if args.threshold is not None:
+ tracker.check_price_threshold(args.threshold, args.threshold_type)
+ return
+
+ # Determine plot settings
+ show_plots = args.plot and not args.no_plot
+
+ # Track prices
+ if args.weekly:
+ tracker.track_weekly(
+ weeks=args.weeks,
+ plot=show_plots,
+ save_plots=args.save_plots,
+ send_alert=args.alert
+ )
+ elif args.daily or (not args.daily and not args.weekly):
+ # Default to daily if neither specified
+ tracker.track_daily(
+ days=args.days,
+ plot=show_plots,
+ save_plots=args.save_plots,
+ send_alert=args.alert
+ )
+
+ print("\nā
Stock tracking complete!")
+ print("="*60 + "\n")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/Stock-Price-Tracker/utils/__init__.py b/Stock-Price-Tracker/utils/__init__.py
new file mode 100644
index 0000000..965527b
--- /dev/null
+++ b/Stock-Price-Tracker/utils/__init__.py
@@ -0,0 +1,9 @@
+"""
+Stock Price Tracker Utilities
+"""
+
+from .yahoo_finance import StockDataFetcher
+from .plotter import StockPlotter
+from .telegram_alert import TelegramAlert
+
+__all__ = ['StockDataFetcher', 'StockPlotter', 'TelegramAlert']
diff --git a/Stock-Price-Tracker/utils/plotter.py b/Stock-Price-Tracker/utils/plotter.py
new file mode 100644
index 0000000..c7c229a
--- /dev/null
+++ b/Stock-Price-Tracker/utils/plotter.py
@@ -0,0 +1,339 @@
+"""
+Stock Price Plotter
+Creates various charts and visualizations for stock data
+"""
+
+import pandas as pd
+import matplotlib.pyplot as plt
+import matplotlib.dates as mdates
+import plotly.graph_objects as go
+from plotly.subplots import make_subplots
+from datetime import datetime
+from typing import Optional
+
+
+class StockPlotter:
+ """Create visualizations for stock data"""
+
+ def __init__(self, ticker: str):
+ """
+ Initialize the stock plotter
+
+ Args:
+ ticker (str): Stock ticker symbol
+ """
+ self.ticker = ticker.upper()
+
+ def plot_price_trend(self, data: pd.DataFrame, title: str = None,
+ save_path: Optional[str] = None, show_plot: bool = True):
+ """
+ Plot stock price trend using matplotlib
+
+ Args:
+ data (pd.DataFrame): Stock data with 'Close' column
+ title (str): Plot title
+ save_path (str): Path to save the plot
+ show_plot (bool): Whether to display the plot
+ """
+ if data.empty:
+ print("No data to plot")
+ return
+
+ plt.figure(figsize=(14, 7))
+
+ # Plot closing price
+ plt.plot(data.index, data['Close'], label='Close Price',
+ color='#2E86C1', linewidth=2)
+
+ # Add moving averages
+ if len(data) >= 7:
+ ma7 = data['Close'].rolling(window=7).mean()
+ plt.plot(data.index, ma7, label='7-Day MA',
+ color='#E67E22', linestyle='--', linewidth=1.5)
+
+ if len(data) >= 30:
+ ma30 = data['Close'].rolling(window=30).mean()
+ plt.plot(data.index, ma30, label='30-Day MA',
+ color='#27AE60', linestyle='--', linewidth=1.5)
+
+ # Formatting
+ if title is None:
+ title = f'{self.ticker} Stock Price Trend'
+ plt.title(title, fontsize=16, fontweight='bold', pad=20)
+ plt.xlabel('Date', fontsize=12, fontweight='bold')
+ plt.ylabel('Price ($)', fontsize=12, fontweight='bold')
+ plt.legend(loc='best', fontsize=10)
+ plt.grid(True, alpha=0.3, linestyle='--')
+
+ # Format x-axis
+ plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
+ plt.gcf().autofmt_xdate()
+
+ plt.tight_layout()
+
+ if save_path:
+ plt.savefig(save_path, dpi=300, bbox_inches='tight')
+ print(f"Plot saved to {save_path}")
+
+ if show_plot:
+ plt.show()
+ else:
+ plt.close()
+
+ def plot_candlestick(self, data: pd.DataFrame, title: str = None,
+ save_path: Optional[str] = None, show_plot: bool = True):
+ """
+ Create interactive candlestick chart using plotly
+
+ Args:
+ data (pd.DataFrame): Stock data with OHLC columns
+ title (str): Chart title
+ save_path (str): Path to save the chart
+ show_plot (bool): Whether to display the chart
+ """
+ if data.empty:
+ print("No data to plot")
+ return
+
+ # Create figure with secondary y-axis
+ fig = make_subplots(
+ rows=2, cols=1,
+ shared_xaxes=True,
+ vertical_spacing=0.03,
+ row_heights=[0.7, 0.3],
+ subplot_titles=(f'{self.ticker} Price', 'Volume')
+ )
+
+ # Candlestick chart
+ fig.add_trace(
+ go.Candlestick(
+ x=data.index,
+ open=data['Open'],
+ high=data['High'],
+ low=data['Low'],
+ close=data['Close'],
+ name='OHLC',
+ increasing_line_color='#26A69A',
+ decreasing_line_color='#EF5350'
+ ),
+ row=1, col=1
+ )
+
+ # Volume bar chart
+ colors = ['#26A69A' if close >= open else '#EF5350'
+ for close, open in zip(data['Close'], data['Open'])]
+
+ fig.add_trace(
+ go.Bar(
+ x=data.index,
+ y=data['Volume'],
+ name='Volume',
+ marker_color=colors,
+ showlegend=False
+ ),
+ row=2, col=1
+ )
+
+ # Update layout
+ if title is None:
+ title = f'{self.ticker} Candlestick Chart'
+
+ fig.update_layout(
+ title=title,
+ title_font_size=18,
+ xaxis_rangeslider_visible=False,
+ height=700,
+ template='plotly_white',
+ hovermode='x unified'
+ )
+
+ fig.update_xaxes(title_text="Date", row=2, col=1)
+ fig.update_yaxes(title_text="Price ($)", row=1, col=1)
+ fig.update_yaxes(title_text="Volume", row=2, col=1)
+
+ if save_path:
+ fig.write_html(save_path)
+ print(f"Interactive chart saved to {save_path}")
+
+ if show_plot:
+ fig.show()
+
+ def plot_volume_analysis(self, data: pd.DataFrame, title: str = None,
+ save_path: Optional[str] = None, show_plot: bool = True):
+ """
+ Plot volume analysis
+
+ Args:
+ data (pd.DataFrame): Stock data with Volume column
+ title (str): Plot title
+ save_path (str): Path to save the plot
+ show_plot (bool): Whether to display the plot
+ """
+ if data.empty:
+ print("No data to plot")
+ return
+
+ fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(14, 8),
+ height_ratios=[2, 1], sharex=True)
+
+ # Price subplot
+ ax1.plot(data.index, data['Close'], label='Close Price',
+ color='#2E86C1', linewidth=2)
+ ax1.set_ylabel('Price ($)', fontsize=12, fontweight='bold')
+ ax1.legend(loc='best')
+ ax1.grid(True, alpha=0.3)
+
+ # Volume subplot
+ colors = ['#26A69A' if close >= open else '#EF5350'
+ for close, open in zip(data['Close'], data['Open'])]
+ ax2.bar(data.index, data['Volume'], color=colors, alpha=0.7)
+ ax2.set_xlabel('Date', fontsize=12, fontweight='bold')
+ ax2.set_ylabel('Volume', fontsize=12, fontweight='bold')
+ ax2.grid(True, alpha=0.3, axis='y')
+
+ # Add average volume line
+ avg_volume = data['Volume'].mean()
+ ax2.axhline(y=avg_volume, color='orange', linestyle='--',
+ linewidth=2, label=f'Avg Volume: {avg_volume:,.0f}')
+ ax2.legend(loc='best')
+
+ if title is None:
+ title = f'{self.ticker} Price & Volume Analysis'
+ fig.suptitle(title, fontsize=16, fontweight='bold')
+
+ plt.gcf().autofmt_xdate()
+ plt.tight_layout()
+
+ if save_path:
+ plt.savefig(save_path, dpi=300, bbox_inches='tight')
+ print(f"Volume analysis plot saved to {save_path}")
+
+ if show_plot:
+ plt.show()
+ else:
+ plt.close()
+
+ def plot_comparison(self, data: pd.DataFrame, comparison_type: str = 'daily',
+ save_path: Optional[str] = None, show_plot: bool = True):
+ """
+ Create comparison plots (daily vs weekly)
+
+ Args:
+ data (pd.DataFrame): Stock data
+ comparison_type (str): Type of comparison ('daily' or 'weekly')
+ save_path (str): Path to save the plot
+ show_plot (bool): Whether to display the plot
+ """
+ if data.empty:
+ print("No data to plot")
+ return
+
+ fig, axes = plt.subplots(2, 2, figsize=(16, 10))
+ fig.suptitle(f'{self.ticker} Stock Analysis - {comparison_type.capitalize()}',
+ fontsize=16, fontweight='bold')
+
+ # Price trend
+ axes[0, 0].plot(data.index, data['Close'], color='#2E86C1', linewidth=2)
+ axes[0, 0].set_title('Closing Price', fontweight='bold')
+ axes[0, 0].set_ylabel('Price ($)')
+ axes[0, 0].grid(True, alpha=0.3)
+
+ # Volume
+ axes[0, 1].bar(data.index, data['Volume'], color='#3498DB', alpha=0.7)
+ axes[0, 1].set_title('Trading Volume', fontweight='bold')
+ axes[0, 1].set_ylabel('Volume')
+ axes[0, 1].grid(True, alpha=0.3, axis='y')
+
+ # Daily returns
+ returns = data['Close'].pct_change() * 100
+ axes[1, 0].plot(data.index, returns, color='#27AE60', linewidth=1.5)
+ axes[1, 0].axhline(y=0, color='red', linestyle='--', linewidth=1)
+ axes[1, 0].set_title('Daily Returns (%)', fontweight='bold')
+ axes[1, 0].set_ylabel('Return (%)')
+ axes[1, 0].set_xlabel('Date')
+ axes[1, 0].grid(True, alpha=0.3)
+
+ # Price distribution
+ axes[1, 1].hist(data['Close'], bins=30, color='#E67E22', alpha=0.7, edgecolor='black')
+ axes[1, 1].set_title('Price Distribution', fontweight='bold')
+ axes[1, 1].set_xlabel('Price ($)')
+ axes[1, 1].set_ylabel('Frequency')
+ axes[1, 1].grid(True, alpha=0.3, axis='y')
+
+ plt.tight_layout()
+
+ if save_path:
+ plt.savefig(save_path, dpi=300, bbox_inches='tight')
+ print(f"Comparison plot saved to {save_path}")
+
+ if show_plot:
+ plt.show()
+ else:
+ plt.close()
+
+ def create_summary_report(self, data: pd.DataFrame, stats: dict,
+ save_path: Optional[str] = None, show_plot: bool = True):
+ """
+ Create a comprehensive visual summary report
+
+ Args:
+ data (pd.DataFrame): Stock data
+ stats (dict): Price change statistics
+ save_path (str): Path to save the report
+ show_plot (bool): Whether to display the report
+ """
+ if data.empty or not stats:
+ print("Insufficient data for summary report")
+ return
+
+ fig = plt.figure(figsize=(16, 10))
+ gs = fig.add_gridspec(3, 2, hspace=0.3, wspace=0.3)
+
+ # Title
+ fig.suptitle(f'{self.ticker} Stock Analysis Report',
+ fontsize=18, fontweight='bold', y=0.98)
+
+ # Main price chart
+ ax1 = fig.add_subplot(gs[0:2, :])
+ ax1.plot(data.index, data['Close'], label='Close Price',
+ color='#2E86C1', linewidth=2.5)
+ ax1.fill_between(data.index, data['Low'], data['High'],
+ alpha=0.2, color='#85C1E9', label='Daily Range')
+ ax1.set_title('Price Trend with Daily Range', fontweight='bold', fontsize=14)
+ ax1.set_ylabel('Price ($)', fontsize=12)
+ ax1.legend(loc='best')
+ ax1.grid(True, alpha=0.3)
+
+ # Statistics text box
+ ax2 = fig.add_subplot(gs[2, 0])
+ ax2.axis('off')
+ stats_text = f"""
+ STATISTICS SUMMARY
+ āāāāāāāāāāāāāāāāāāāāāāāāā
+ Start Price: ${stats.get('start_price', 'N/A')}
+ End Price: ${stats.get('end_price', 'N/A')}
+ Change: ${stats.get('change', 'N/A')} ({stats.get('change_percent', 'N/A')}%)
+ Highest: ${stats.get('highest', 'N/A')}
+ Lowest: ${stats.get('lowest', 'N/A')}
+ Range: ${stats.get('highest', 0) - stats.get('lowest', 0):.2f}
+ """
+ ax2.text(0.1, 0.5, stats_text, fontsize=11, family='monospace',
+ verticalalignment='center', bbox=dict(boxstyle='round',
+ facecolor='wheat', alpha=0.5))
+
+ # Volume chart
+ ax3 = fig.add_subplot(gs[2, 1])
+ ax3.bar(data.index, data['Volume'], color='#3498DB', alpha=0.6)
+ ax3.set_title('Trading Volume', fontweight='bold', fontsize=12)
+ ax3.set_ylabel('Volume', fontsize=10)
+ ax3.grid(True, alpha=0.3, axis='y')
+ ax3.tick_params(axis='x', rotation=45)
+
+ if save_path:
+ plt.savefig(save_path, dpi=300, bbox_inches='tight')
+ print(f"Summary report saved to {save_path}")
+
+ if show_plot:
+ plt.show()
+ else:
+ plt.close()
diff --git a/Stock-Price-Tracker/utils/telegram_alert.py b/Stock-Price-Tracker/utils/telegram_alert.py
new file mode 100644
index 0000000..d36797b
--- /dev/null
+++ b/Stock-Price-Tracker/utils/telegram_alert.py
@@ -0,0 +1,286 @@
+"""
+Telegram Alert System
+Send stock price alerts via Telegram bot
+"""
+
+import os
+from typing import Optional, Dict, Any
+from datetime import datetime
+from telegram import Bot
+from telegram.error import TelegramError
+import asyncio
+
+
+class TelegramAlert:
+ """Send alerts via Telegram bot"""
+
+ def __init__(self, bot_token: Optional[str] = None, chat_id: Optional[str] = None):
+ """
+ Initialize Telegram bot
+
+ Args:
+ bot_token (str): Telegram bot token from @BotFather
+ chat_id (str): Telegram chat ID to send messages to
+ """
+ self.bot_token = bot_token or os.getenv('TELEGRAM_BOT_TOKEN')
+ self.chat_id = chat_id or os.getenv('TELEGRAM_CHAT_ID')
+
+ if not self.bot_token or not self.chat_id:
+ print("ā ļø Warning: Telegram credentials not configured")
+ print("Set TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID in .env file")
+ self.bot = None
+ else:
+ self.bot = Bot(token=self.bot_token)
+
+ def is_configured(self) -> bool:
+ """Check if Telegram bot is properly configured"""
+ return self.bot is not None
+
+ async def send_message_async(self, message: str) -> bool:
+ """
+ Send a text message via Telegram (async)
+
+ Args:
+ message (str): Message to send
+
+ Returns:
+ bool: True if successful, False otherwise
+ """
+ if not self.is_configured():
+ print("Telegram bot not configured. Skipping alert.")
+ return False
+
+ try:
+ await self.bot.send_message(
+ chat_id=self.chat_id,
+ text=message,
+ parse_mode='HTML'
+ )
+ return True
+ except TelegramError as e:
+ print(f"Error sending Telegram message: {e}")
+ return False
+
+ def send_message(self, message: str) -> bool:
+ """
+ Send a text message via Telegram (sync wrapper)
+
+ Args:
+ message (str): Message to send
+
+ Returns:
+ bool: True if successful, False otherwise
+ """
+ if not self.is_configured():
+ print("Telegram bot not configured. Skipping alert.")
+ return False
+
+ try:
+ # Run async function in sync context
+ loop = asyncio.new_event_loop()
+ asyncio.set_event_loop(loop)
+ result = loop.run_until_complete(self.send_message_async(message))
+ loop.close()
+ return result
+ except Exception as e:
+ print(f"Error in sync message send: {e}")
+ return False
+
+ def format_stock_alert(self, ticker: str, stats: Dict[str, Any],
+ alert_type: str = "update") -> str:
+ """
+ Format stock data into a nice alert message
+
+ Args:
+ ticker (str): Stock ticker symbol
+ stats (dict): Stock statistics
+ alert_type (str): Type of alert ('update', 'threshold', 'daily_summary')
+
+ Returns:
+ str: Formatted message
+ """
+ timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
+
+ # Emoji based on price change
+ change_percent = stats.get('change_percent', 0)
+ if change_percent > 0:
+ emoji = "š"
+ direction = "UP"
+ elif change_percent < 0:
+ emoji = "š"
+ direction = "DOWN"
+ else:
+ emoji = "ā”ļø"
+ direction = "FLAT"
+
+ if alert_type == "threshold":
+ message = f"""
+šØ PRICE ALERT: {ticker} šØ
+
+{emoji} Price moved {direction} by {abs(change_percent):.2f}%!
+
+š° Current Price: ${stats.get('end_price', 'N/A')}
+š Previous Price: ${stats.get('start_price', 'N/A')}
+š Change: ${stats.get('change', 'N/A')} ({change_percent:+.2f}%)
+
+š
Time: {timestamp}
+"""
+ elif alert_type == "daily_summary":
+ message = f"""
+š DAILY SUMMARY: {ticker}
+
+{emoji} Overall: {direction} {abs(change_percent):.2f}%
+
+š° Opening: ${stats.get('start_price', 'N/A')}
+šµ Closing: ${stats.get('end_price', 'N/A')}
+š¼ High: ${stats.get('highest', 'N/A')}
+š½ Low: ${stats.get('lowest', 'N/A')}
+š Change: ${stats.get('change', 'N/A')} ({change_percent:+.2f}%)
+
+š
Date: {timestamp}
+"""
+ else: # update
+ message = f"""
+š Stock Update: {ticker}
+
+š° Current Price: ${stats.get('end_price', 'N/A')}
+š Change: ${stats.get('change', 'N/A')} ({change_percent:+.2f}%) {emoji}
+š¼ High: ${stats.get('highest', 'N/A')}
+š½ Low: ${stats.get('lowest', 'N/A')}
+
+š
Updated: {timestamp}
+"""
+
+ return message.strip()
+
+ def send_stock_alert(self, ticker: str, stats: Dict[str, Any],
+ alert_type: str = "update") -> bool:
+ """
+ Send a formatted stock alert
+
+ Args:
+ ticker (str): Stock ticker symbol
+ stats (dict): Stock statistics
+ alert_type (str): Type of alert
+
+ Returns:
+ bool: True if successful
+ """
+ message = self.format_stock_alert(ticker, stats, alert_type)
+ return self.send_message(message)
+
+ def send_price_threshold_alert(self, ticker: str, current_price: float,
+ threshold: float, threshold_type: str = "above") -> bool:
+ """
+ Send alert when price crosses a threshold
+
+ Args:
+ ticker (str): Stock ticker symbol
+ current_price (float): Current stock price
+ threshold (float): Price threshold
+ threshold_type (str): 'above' or 'below'
+
+ Returns:
+ bool: True if successful
+ """
+ emoji = "š" if threshold_type == "above" else "ā ļø"
+ arrow = "āļø" if threshold_type == "above" else "āļø"
+
+ message = f"""
+{emoji} THRESHOLD ALERT: {ticker}
+
+{arrow} Price is now {threshold_type} ${threshold:.2f}!
+
+š° Current Price: ${current_price:.2f}
+šÆ Threshold: ${threshold:.2f}
+
+š
Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
+"""
+
+ return self.send_message(message.strip())
+
+ def send_error_alert(self, ticker: str, error_message: str) -> bool:
+ """
+ Send error notification
+
+ Args:
+ ticker (str): Stock ticker symbol
+ error_message (str): Error description
+
+ Returns:
+ bool: True if successful
+ """
+ message = f"""
+ā ERROR: {ticker}
+
+ā ļø Issue: {error_message}
+
+š
Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
+
+Please check your stock tracker configuration.
+"""
+
+ return self.send_message(message.strip())
+
+ def send_welcome_message(self, ticker: str) -> bool:
+ """
+ Send welcome/initialization message
+
+ Args:
+ ticker (str): Stock ticker symbol
+
+ Returns:
+ bool: True if successful
+ """
+ message = f"""
+š Stock Price Tracker Started
+
+š Now tracking: {ticker}
+
+You will receive alerts when:
+⢠Significant price changes occur
+⢠Daily summaries are available
+⢠Threshold levels are crossed
+
+š” Stay informed about your investments!
+"""
+
+ return self.send_message(message.strip())
+
+ async def test_connection_async(self) -> bool:
+ """
+ Test Telegram bot connection (async)
+
+ Returns:
+ bool: True if connection is successful
+ """
+ if not self.is_configured():
+ return False
+
+ try:
+ bot_info = await self.bot.get_me()
+ print(f"ā
Connected to Telegram bot: @{bot_info.username}")
+ return True
+ except TelegramError as e:
+ print(f"ā Failed to connect to Telegram: {e}")
+ return False
+
+ def test_connection(self) -> bool:
+ """
+ Test Telegram bot connection (sync wrapper)
+
+ Returns:
+ bool: True if connection is successful
+ """
+ if not self.is_configured():
+ return False
+
+ try:
+ loop = asyncio.new_event_loop()
+ asyncio.set_event_loop(loop)
+ result = loop.run_until_complete(self.test_connection_async())
+ loop.close()
+ return result
+ except Exception as e:
+ print(f"Error testing connection: {e}")
+ return False
diff --git a/Stock-Price-Tracker/utils/yahoo_finance.py b/Stock-Price-Tracker/utils/yahoo_finance.py
new file mode 100644
index 0000000..9ba07d8
--- /dev/null
+++ b/Stock-Price-Tracker/utils/yahoo_finance.py
@@ -0,0 +1,205 @@
+"""
+Yahoo Finance Data Fetcher
+Fetches stock data using yfinance API
+"""
+
+import yfinance as yf
+import pandas as pd
+from datetime import datetime, timedelta
+from typing import Optional, Dict, Any
+
+
+class StockDataFetcher:
+ """Fetch stock data from Yahoo Finance API"""
+
+ def __init__(self, ticker: str):
+ """
+ Initialize the stock data fetcher
+
+ Args:
+ ticker (str): Stock ticker symbol (e.g., 'AAPL', 'GOOGL', 'TSLA')
+ """
+ self.ticker = ticker.upper()
+ self.stock = yf.Ticker(self.ticker)
+
+ def get_stock_info(self) -> Dict[str, Any]:
+ """
+ Get basic stock information
+
+ Returns:
+ dict: Stock information including name, sector, market cap, etc.
+ """
+ try:
+ info = self.stock.info
+ return {
+ 'symbol': self.ticker,
+ 'name': info.get('longName', 'N/A'),
+ 'sector': info.get('sector', 'N/A'),
+ 'industry': info.get('industry', 'N/A'),
+ 'market_cap': info.get('marketCap', 'N/A'),
+ 'current_price': info.get('currentPrice', info.get('regularMarketPrice', 'N/A')),
+ 'previous_close': info.get('previousClose', 'N/A'),
+ 'volume': info.get('volume', 'N/A'),
+ 'average_volume': info.get('averageVolume', 'N/A'),
+ '52_week_high': info.get('fiftyTwoWeekHigh', 'N/A'),
+ '52_week_low': info.get('fiftyTwoWeekLow', 'N/A'),
+ }
+ except Exception as e:
+ print(f"Error fetching stock info: {e}")
+ return {}
+
+ def get_historical_data(self, period: str = '1mo', interval: str = '1d') -> pd.DataFrame:
+ """
+ Get historical stock data
+
+ Args:
+ period (str): Time period ('1d', '5d', '1mo', '3mo', '6mo', '1y', '2y', '5y', 'max')
+ interval (str): Data interval ('1m', '5m', '15m', '1h', '1d', '1wk', '1mo')
+
+ Returns:
+ pd.DataFrame: Historical stock data with OHLCV (Open, High, Low, Close, Volume)
+ """
+ try:
+ data = self.stock.history(period=period, interval=interval)
+ if data.empty:
+ print(f"No data found for {self.ticker}")
+ return pd.DataFrame()
+ return data
+ except Exception as e:
+ print(f"Error fetching historical data: {e}")
+ return pd.DataFrame()
+
+ def get_data_by_date_range(self, start_date: str, end_date: str, interval: str = '1d') -> pd.DataFrame:
+ """
+ Get historical data for a specific date range
+
+ Args:
+ start_date (str): Start date in 'YYYY-MM-DD' format
+ end_date (str): End date in 'YYYY-MM-DD' format
+ interval (str): Data interval ('1d', '1wk', '1mo')
+
+ Returns:
+ pd.DataFrame: Historical stock data
+ """
+ try:
+ data = self.stock.history(start=start_date, end=end_date, interval=interval)
+ if data.empty:
+ print(f"No data found for {self.ticker} between {start_date} and {end_date}")
+ return pd.DataFrame()
+ return data
+ except Exception as e:
+ print(f"Error fetching data by date range: {e}")
+ return pd.DataFrame()
+
+ def get_daily_data(self, days: int = 30) -> pd.DataFrame:
+ """
+ Get daily stock data for the specified number of days
+
+ Args:
+ days (int): Number of days to fetch (default: 30)
+
+ Returns:
+ pd.DataFrame: Daily stock data
+ """
+ period_map = {
+ 5: '5d',
+ 7: '1mo',
+ 30: '1mo',
+ 90: '3mo',
+ 180: '6mo',
+ 365: '1y'
+ }
+
+ # Find closest period
+ period = '1mo'
+ for day_limit, p in sorted(period_map.items()):
+ if days <= day_limit:
+ period = p
+ break
+ if days > 365:
+ period = 'max'
+
+ return self.get_historical_data(period=period, interval='1d')
+
+ def get_weekly_data(self, weeks: int = 12) -> pd.DataFrame:
+ """
+ Get weekly stock data
+
+ Args:
+ weeks (int): Number of weeks to fetch (default: 12)
+
+ Returns:
+ pd.DataFrame: Weekly stock data
+ """
+ if weeks <= 4:
+ period = '1mo'
+ elif weeks <= 12:
+ period = '3mo'
+ elif weeks <= 26:
+ period = '6mo'
+ else:
+ period = '1y'
+
+ return self.get_historical_data(period=period, interval='1wk')
+
+ def calculate_price_change(self, data: pd.DataFrame) -> Dict[str, float]:
+ """
+ Calculate price change statistics
+
+ Args:
+ data (pd.DataFrame): Stock data
+
+ Returns:
+ dict: Price change statistics (absolute, percentage, high, low)
+ """
+ if data.empty:
+ return {}
+
+ try:
+ start_price = data['Close'].iloc[0]
+ end_price = data['Close'].iloc[-1]
+ highest_price = data['High'].max()
+ lowest_price = data['Low'].min()
+
+ change = end_price - start_price
+ change_percent = (change / start_price) * 100
+
+ return {
+ 'start_price': round(start_price, 2),
+ 'end_price': round(end_price, 2),
+ 'change': round(change, 2),
+ 'change_percent': round(change_percent, 2),
+ 'highest': round(highest_price, 2),
+ 'lowest': round(lowest_price, 2)
+ }
+ except Exception as e:
+ print(f"Error calculating price change: {e}")
+ return {}
+
+ def get_current_price(self) -> Optional[float]:
+ """
+ Get the current/latest stock price
+
+ Returns:
+ float: Current price or None if unavailable
+ """
+ try:
+ info = self.stock.info
+ price = info.get('currentPrice', info.get('regularMarketPrice'))
+ return price
+ except Exception as e:
+ print(f"Error fetching current price: {e}")
+ return None
+
+ def is_valid_ticker(self) -> bool:
+ """
+ Check if the ticker symbol is valid
+
+ Returns:
+ bool: True if valid, False otherwise
+ """
+ try:
+ info = self.stock.info
+ return 'symbol' in info or 'longName' in info
+ except:
+ return False