Automated system that analyzes news for FNILX (Fidelity ZERO Large Cap Index Fund) by aggregating news from its top 50 holdings, plus individual analysis for UURAF. Generates AI-powered sentiment summaries and delivers daily reports via Telegram at 5:00 PM EST using GitHub Actions.
- Automated News Aggregation: Fetches news from Finnhub API for 51 tickers (FNILX top 50 + UURAF)
- AI-Powered Analysis: Uses Groq LLM (Llama 3.1) for sentiment analysis
- Fund-Level Insights: Aggregates all FNILX holdings into single fund-level sentiment
- Individual Stock Tracking: Separate analysis for UURAF with sector-specific focus
- Daily Telegram Reports: Formatted reports delivered at 5 PM EST
- Historical Tracking: CSV logging for sentiment trend analysis
- Production-Ready: Robust error handling, rate limiting, and monitoring
Pipeline Flow:
- Ingestion: Finnhub API fetches news for 51 tickers
- Aggregation: Combines all FNILX holdings news into single dataset
- Analysis: Groq LLM generates two sentiment analyses (FNILX aggregate + UURAF individual)
- Storage: Logs results to CSV for trend tracking
- Delivery: Sends formatted summary to Telegram
- Python 3.11+
- Free API keys:
- Finnhub - Stock news data
- Groq - LLM for sentiment analysis
- Telegram Bot - Message delivery
git clone <your-repo-url>
cd CloseNotice# Create virtual environment
python -m venv venv
# Activate virtual environment
# Windows:
venv\Scripts\activate
# macOS/Linux:
source venv/bin/activate
# Install dependencies
pip install -r requirements.txtCreate a .env file in the project root:
cp .env.example .envEdit .env with your actual API keys:
FINNHUB_API_KEY=your_finnhub_api_key_here
GROQ_API_KEY=your_groq_api_key_here
TELEGRAM_BOT_TOKEN=your_telegram_bot_token_here
TELEGRAM_CHAT_ID=your_telegram_chat_id_hereFinnhub:
- Register at https://finnhub.io/register
- Copy your API key from the dashboard
- Free tier: 60 calls/minute, 1000 calls/day (sufficient for 51 tickers)
Groq:
- Sign up at https://console.groq.com
- Navigate to API Keys section
- Create new key
- Free tier: Generous limits for Llama 3.1 model
Telegram Bot:
- Open Telegram and search for @BotFather
- Send
/newbotand follow instructions - Copy the bot token
- Send
/startto your new bot - Get your chat ID:
- Visit:
https://api.telegram.org/bot<YOUR_BOT_TOKEN>/getUpdates - Send a message to your bot
- Refresh the URL and find your chat ID in the response
- Visit:
# Run analysis
python -m src.mainThis will:
- Fetch news for all 51 tickers
- Analyze sentiment for FNILX and UURAF
- Send report to Telegram
- Log results to
data/sentiment_history.csv
git init
git add .
git commit -m "Initial commit: Stock News Analysis System"
git remote add origin <your-github-repo-url>
git push -u origin mainGo to: Repository → Settings → Secrets and variables → Actions
Add the following secrets:
FINNHUB_API_KEYGROQ_API_KEYTELEGRAM_BOT_TOKENTELEGRAM_CHAT_ID
The workflow is already configured in .github/workflows/daily_analysis.yml
Manual Trigger (for testing):
- Go to Actions tab
- Select Daily Stock Analysis
- Click Run workflow
- Check Telegram for report
Scheduled Run:
- Runs automatically daily at 5:00 PM EST (10:00 PM UTC)
- Important: Adjust cron schedule for daylight saving time:
- EST (Nov-Mar):
0 22 * * *(currently configured) - EDT (Mar-Nov):
0 21 * * *(uncomment in workflow file)
- EST (Nov-Mar):
CloseNotice/
├── .github/workflows/
│ └── daily_analysis.yml # GitHub Actions cron job
├── src/
│ ├── main.py # Orchestration entry point
│ ├── config/
│ │ ├── settings.py # Environment variables & config
│ │ └── tickers.py # FNILX top 50 + UURAF watchlist
│ ├── data/
│ │ └── finnhub_client.py # News fetching with rate limiting
│ ├── analysis/
│ │ ├── groq_client.py # LLM sentiment analysis
│ │ └── prompts.py # Prompt templates
│ ├── delivery/
│ │ └── telegram_client.py # Message formatting & sending
│ ├── storage/
│ │ └── csv_logger.py # Sentiment history tracking
│ └── utils/
│ ├── error_handler.py # Error handling & alerts
│ └── logger.py # Logging configuration
├── data/
│ └── sentiment_history.csv # Persistent sentiment log
├── logs/
│ └── analysis.log # Application logs
├── .env.example # Template for API keys
├── .gitignore # Git ignore rules
├── requirements.txt # Python dependencies
└── README.md # This file
The top 50 FNILX holdings are configured in src/config/tickers.py.
When to update:
- Quarterly fund rebalancing
- Significant portfolio changes
- New ticker replacements
How to update:
- Visit FNILX Holdings
- Update
FNILX_TOP50_WITH_SECTORSdict insrc/config/tickers.py - Include sector tags (e.g.,
"NVDA": "Tech/AI") - Update
TICKER_METADATAwith company names
To track additional stocks separately (like UURAF):
# In src/config/tickers.py
INDIVIDUAL_TICKERS_WITH_SECTORS = {
"UURAF": "Energy/Uranium",
"NEWSTOCK": "Tech/Software" # Add new stocks here
}Modify src/config/settings.py or use environment variables:
# Rate limiting
FINNHUB_RATE_LIMIT=60 # Calls per minute
API_CALL_DELAY=1.1 # Seconds between calls
# News fetching
MAX_ARTICLES_PER_TICKER=1 # Articles per ticker
DEFAULT_LOOKBACK_HOURS=24 # Normal lookback period
WEEKEND_LOOKBACK_HOURS=72 # Weekend lookback period
# LLM configuration
GROQ_MODEL=llama-3.1-8b-instant # Model to use
GROQ_TEMPERATURE=0.3 # Temperature (0-1)
MAX_SUMMARY_LENGTH=200 # Chars to truncate summaries# Full production run
python -m src.main
# Test Telegram connection
python -c "from src.delivery.telegram_client import TelegramClient; TelegramClient().send_test_message()"
# Test Finnhub connection (fetch AAPL news)
python -c "from src.data.finnhub_client import FinnhubClient; print(FinnhubClient().fetch_company_news('AAPL', '2026-01-18', '2026-01-19'))"from src.storage.csv_logger import SentimentLogger
logger = SentimentLogger()
# Get FNILX trend over 30 days
trend = logger.get_sentiment_trend("FNILX", days=30)
print(f"Average sentiment: {trend['mean']:.1f}/10")
print(f"Trend: {'↑' if trend['trend'] > 0 else '↓'}")
# Get latest sentiment
latest = logger.get_latest_sentiment("UURAF")
print(f"Latest UURAF sentiment: {latest['sentiment_score']}/10")Symptom: Finnhub returns 429 status code
Solutions:
- Increase
API_CALL_DELAYin.env:API_CALL_DELAY=1.5
- The system automatically retries with exponential backoff
Symptom: LLM analysis fails after retries
Solutions:
- Check Groq status page
- System will send Telegram alert for extended outages
- Retry manually:
python -m src.main
Symptom: Analysis succeeds but no Telegram message
Solutions:
- Verify bot token and chat ID:
python -c "from src.delivery.telegram_client import TelegramClient; TelegramClient().send_test_message()" - Check bot permissions (must be able to send messages)
- Ensure you've sent
/startto the bot
Symptom: CSV history not updated after workflow runs
Solutions:
- Check workflow logs for git errors
- Ensure repository permissions allow Actions to push
- Verify
GITHUB_TOKENhas write permissions (Settings → Actions → General → Workflow permissions)
Symptom: "Market Quiet" notification on trading days
Solutions:
- This is expected on weekends/holidays
- System automatically uses 72-hour lookback on Monday/Sunday
- If occurring on trading days, check Finnhub API status
Typical Run:
- News fetching: ~56 seconds (51 tickers × 1.1s delay)
- LLM analysis: ~10-15 seconds (2 API calls)
- CSV logging + Telegram: ~2 seconds
- Total runtime: ~70-75 seconds
API Usage:
- Finnhub: 51 calls/day (well within 1000/day free tier)
- Groq: 2 calls/day (minimal usage, free tier sufficient)
- Telegram: 1-2 messages/day (unlimited free)
Cost: $0/month (all free tier APIs)
The CSV logger tracks sentiment over time. Access historical data:
import pandas as pd
# Load CSV
df = pd.read_csv('data/sentiment_history.csv')
df['timestamp'] = pd.to_datetime(df['timestamp'])
# Plot FNILX sentiment over time
fnilx = df[df['ticker'] == 'FNILX']
fnilx.plot(x='timestamp', y='sentiment_score', title='FNILX Sentiment Trend')Modify src/delivery/telegram_client.py to add custom alerts:
# Alert on significant sentiment change
def check_sentiment_shift(current_score, historical_mean):
if abs(current_score - historical_mean) > 3:
send_alert(f"⚠️ Significant sentiment shift detected!")Extend to track multiple funds:
# In src/config/tickers.py
FUNDS = {
"FNILX": ["NVDA", "AAPL", ...], # Large cap
"FZROX": ["AAPL", "MSFT", ...], # Total market
}Contributions welcome! Please:
- Fork repository
- Create feature branch:
git checkout -b feature/new-feature - Test locally:
python -m src.main - Submit pull request
MIT License - feel free to use and modify.
- Finnhub: Stock news API
- Groq: Fast LLM inference
- Fidelity: FNILX fund data
For issues or questions:
- Check Troubleshooting section
- Review logs:
logs/analysis.log - Open GitHub issue with logs and error details
Note: This system is for informational purposes only. Not financial advice. Always do your own research before making investment decisions.