Initial commit with translated description
This commit is contained in:
155
README.md
Normal file
155
README.md
Normal file
@@ -0,0 +1,155 @@
|
||||
# Finance News Skill for OpenClaw
|
||||
|
||||
AI-powered market news briefings with configurable language output and automated delivery.
|
||||
|
||||
## Features
|
||||
|
||||
- **Multi-source aggregation:** Reuters, WSJ, FT, Bloomberg, CNBC, Yahoo Finance, Tagesschau, Handelsblatt
|
||||
- **Global markets:** US (S&P, Dow, NASDAQ), Europe (DAX, STOXX, FTSE), Japan (Nikkei)
|
||||
- **AI summaries:** LLM-powered analysis in German or English
|
||||
- **Automated briefings:** Morning (market open) and evening (market close)
|
||||
- **WhatsApp/Telegram delivery:** Send briefings via openclaw
|
||||
- **Portfolio tracking:** Personalized news for your stocks with price alerts
|
||||
- **Lobster workflows:** Approval gates before sending
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Docker (Recommended)
|
||||
|
||||
```bash
|
||||
# Build the Docker image
|
||||
docker build -t finance-news-briefing .
|
||||
|
||||
# Generate a briefing
|
||||
docker run --rm -v "$PWD/config:/app/config:ro" \
|
||||
finance-news-briefing python3 scripts/briefing.py \
|
||||
--time morning --lang de --json --fast
|
||||
```
|
||||
|
||||
### Lobster Workflow
|
||||
|
||||
```bash
|
||||
# Set required environment variables
|
||||
export FINANCE_NEWS_TARGET="your-group-jid@g.us" # WhatsApp JID or Telegram chat ID
|
||||
export FINANCE_NEWS_CHANNEL="whatsapp" # or "telegram"
|
||||
|
||||
# Run workflow (halts for approval before sending)
|
||||
lobster run workflows/briefing.yaml --args-json '{"time":"morning","lang":"de"}'
|
||||
```
|
||||
|
||||
### CLI (Legacy)
|
||||
|
||||
```bash
|
||||
# Generate a briefing
|
||||
finance-news briefing --morning --lang de
|
||||
|
||||
# Use fast mode + deadline (recommended)
|
||||
finance-news briefing --morning --lang de --fast --deadline 300
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Description | Example |
|
||||
|----------|-------------|---------|
|
||||
| `FINANCE_NEWS_TARGET` | Delivery target (WhatsApp JID, group name, or Telegram chat ID) | `120363421796203667@g.us` |
|
||||
| `FINANCE_NEWS_CHANNEL` | Delivery channel | `whatsapp` or `telegram` |
|
||||
| `SKILL_DIR` | Path to skill directory (for Lobster) | `$HOME/projects/finance-news-openclaw-skill` |
|
||||
|
||||
## Installation
|
||||
|
||||
### Option 1: Docker (Recommended)
|
||||
|
||||
```bash
|
||||
git clone https://github.com/kesslerio/finance-news-openclaw-skill.git
|
||||
cd finance-news-openclaw-skill
|
||||
docker build -t finance-news-briefing .
|
||||
```
|
||||
|
||||
### Option 2: Native Python
|
||||
|
||||
```bash
|
||||
# Clone repository
|
||||
git clone https://github.com/kesslerio/finance-news-openclaw-skill.git \
|
||||
~/openclaw/skills/finance-news
|
||||
|
||||
# Create virtual environment
|
||||
cd ~/openclaw/skills/finance-news
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
|
||||
# Create CLI symlink
|
||||
ln -sf ~/openclaw/skills/finance-news/scripts/finance-news ~/.local/bin/finance-news
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Configuration is stored in `config/config.json`:
|
||||
|
||||
- **RSS Feeds:** Enable/disable news sources per region
|
||||
- **Markets:** Choose which indices to track
|
||||
- **Delivery:** WhatsApp/Telegram settings
|
||||
- **Language:** German (`de`) or English (`en`) output
|
||||
- **Schedule:** Cron times for morning/evening briefings
|
||||
- **LLM:** Model order preference for headlines, summaries, translations
|
||||
|
||||
Run the setup wizard for interactive configuration:
|
||||
|
||||
```bash
|
||||
finance-news setup
|
||||
```
|
||||
|
||||
## Lobster Workflow
|
||||
|
||||
The skill includes a Lobster workflow (`workflows/briefing.yaml`) that:
|
||||
|
||||
1. **Generates** briefing via Docker
|
||||
2. **Translates** portfolio headlines (German only, via openclaw)
|
||||
3. **Halts** for approval (shows preview)
|
||||
4. **Sends** macro briefing to channel
|
||||
5. **Sends** portfolio briefing to channel
|
||||
|
||||
### Workflow Arguments
|
||||
|
||||
| Arg | Default | Description |
|
||||
|-----|---------|-------------|
|
||||
| `time` | `morning` | Briefing type: `morning` or `evening` |
|
||||
| `lang` | `de` | Language: `en` or `de` |
|
||||
| `channel` | env var | `whatsapp` or `telegram` |
|
||||
| `target` | env var | Group JID/name or chat ID |
|
||||
| `fast` | `false` | Use fast mode (shorter timeouts) |
|
||||
|
||||
## Portfolio
|
||||
|
||||
Manage your stock watchlist in `config/portfolio.csv`:
|
||||
|
||||
```bash
|
||||
finance-news portfolio-list # View portfolio
|
||||
finance-news portfolio-add NVDA # Add stock
|
||||
finance-news portfolio-remove TSLA # Remove stock
|
||||
finance-news portfolio-import stocks.csv # Import from CSV
|
||||
```
|
||||
|
||||
Portfolio briefings show:
|
||||
- Top gainers and losers from your holdings
|
||||
- Relevant news articles with translations
|
||||
- Shortened hyperlinks for easy access
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Python 3.10+
|
||||
- Docker (recommended)
|
||||
- openclaw CLI (for message delivery and LLM)
|
||||
- Lobster (for workflow automation)
|
||||
|
||||
### Optional
|
||||
|
||||
- OpenBB (`openbb-quote`) for enhanced market data
|
||||
|
||||
## License
|
||||
|
||||
Apache 2.0 - See [LICENSE](LICENSE) file for details.
|
||||
|
||||
## Related Skills
|
||||
|
||||
- **[task-tracker](https://github.com/kesslerio/task-tracker-openclaw-skill):** Personal task management with daily standups
|
||||
280
SKILL.md
Normal file
280
SKILL.md
Normal file
@@ -0,0 +1,280 @@
|
||||
---
|
||||
name: finance-news
|
||||
description: "市场新闻简报,包含AI摘要。"
|
||||
---
|
||||
|
||||
# Finance News Skill
|
||||
|
||||
AI-powered market news briefings with configurable language output and automated delivery.
|
||||
|
||||
## First-Time Setup
|
||||
|
||||
Run the interactive setup wizard to configure your sources, delivery channels, and schedule:
|
||||
|
||||
```bash
|
||||
finance-news setup
|
||||
```
|
||||
|
||||
The wizard will guide you through:
|
||||
- 📰 **RSS Feeds:** Enable/disable WSJ, Barron's, CNBC, Yahoo, etc.
|
||||
- 📊 **Markets:** Choose regions (US, Europe, Japan, Asia)
|
||||
- 📤 **Delivery:** Configure WhatsApp/Telegram group
|
||||
- 🌐 **Language:** Set default language (English/German)
|
||||
- ⏰ **Schedule:** Configure morning/evening cron times
|
||||
|
||||
You can also configure specific sections:
|
||||
```bash
|
||||
finance-news setup --section feeds # Just RSS feeds
|
||||
finance-news setup --section delivery # Just delivery channels
|
||||
finance-news setup --section schedule # Just cron schedule
|
||||
finance-news setup --reset # Reset to defaults
|
||||
finance-news config # Show current config
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Generate morning briefing
|
||||
finance-news briefing --morning
|
||||
|
||||
# View market overview
|
||||
finance-news market
|
||||
|
||||
# Get news for your portfolio
|
||||
finance-news portfolio
|
||||
|
||||
# Get news for specific stock
|
||||
finance-news news AAPL
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
### 📊 Market Coverage
|
||||
- **US Markets:** S&P 500, Dow Jones, NASDAQ
|
||||
- **Europe:** DAX, STOXX 50, FTSE 100
|
||||
- **Japan:** Nikkei 225
|
||||
|
||||
### 📰 News Sources
|
||||
- **Premium:** WSJ, Barron's (RSS feeds)
|
||||
- **Free:** CNBC, Yahoo Finance, Finnhub
|
||||
- **Portfolio:** Ticker-specific news from Yahoo
|
||||
|
||||
### 🤖 AI Summaries
|
||||
- Gemini-powered analysis
|
||||
- Configurable language (English/German)
|
||||
- Briefing styles: summary, analysis, headlines
|
||||
|
||||
### 📅 Automated Briefings
|
||||
- **Morning:** 6:30 AM PT (US market open)
|
||||
- **Evening:** 1:00 PM PT (US market close)
|
||||
- **Delivery:** WhatsApp (configure group in cron scripts)
|
||||
|
||||
## Commands
|
||||
|
||||
### Briefing Generation
|
||||
|
||||
```bash
|
||||
# Morning briefing (English is default)
|
||||
finance-news briefing --morning
|
||||
|
||||
# Evening briefing with WhatsApp delivery
|
||||
finance-news briefing --evening --send --group "Market Briefing"
|
||||
|
||||
# German language option
|
||||
finance-news briefing --morning --lang de
|
||||
|
||||
# Analysis style (more detailed)
|
||||
finance-news briefing --style analysis
|
||||
```
|
||||
|
||||
### Market Data
|
||||
|
||||
```bash
|
||||
# Market overview (indices + top headlines)
|
||||
finance-news market
|
||||
|
||||
# JSON output for processing
|
||||
finance-news market --json
|
||||
```
|
||||
|
||||
### Portfolio Management
|
||||
|
||||
```bash
|
||||
# List portfolio
|
||||
finance-news portfolio-list
|
||||
|
||||
# Add stock
|
||||
finance-news portfolio-add NVDA --name "NVIDIA Corporation" --category Tech
|
||||
|
||||
# Remove stock
|
||||
finance-news portfolio-remove TSLA
|
||||
|
||||
# Import from CSV
|
||||
finance-news portfolio-import ~/my_stocks.csv
|
||||
|
||||
# Interactive portfolio creation
|
||||
finance-news portfolio-create
|
||||
```
|
||||
|
||||
### Ticker News
|
||||
|
||||
```bash
|
||||
# News for specific stock
|
||||
finance-news news AAPL
|
||||
finance-news news TSLA
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Portfolio CSV Format
|
||||
|
||||
Location: `~/clawd/skills/finance-news/config/portfolio.csv`
|
||||
|
||||
```csv
|
||||
symbol,name,category,notes
|
||||
AAPL,Apple Inc.,Tech,Core holding
|
||||
NVDA,NVIDIA Corporation,Tech,AI play
|
||||
MSFT,Microsoft Corporation,Tech,
|
||||
```
|
||||
|
||||
### Sources Configuration
|
||||
|
||||
Location: `~/clawd/skills/finance-news/config/config.json` (legacy fallback: `config/sources.json`)
|
||||
|
||||
- RSS feeds for WSJ, Barron's, CNBC, Yahoo
|
||||
- Market indices by region
|
||||
- Language settings
|
||||
|
||||
## Cron Jobs
|
||||
|
||||
### Setup via OpenClaw
|
||||
|
||||
```bash
|
||||
# Add morning briefing cron job
|
||||
openclaw cron add --schedule "30 6 * * 1-5" \
|
||||
--timezone "America/Los_Angeles" \
|
||||
--command "bash ~/clawd/skills/finance-news/cron/morning.sh"
|
||||
|
||||
# Add evening briefing cron job
|
||||
openclaw cron add --schedule "0 13 * * 1-5" \
|
||||
--timezone "America/Los_Angeles" \
|
||||
--command "bash ~/clawd/skills/finance-news/cron/evening.sh"
|
||||
```
|
||||
|
||||
### Manual Cron (crontab)
|
||||
|
||||
```cron
|
||||
# Morning briefing (6:30 AM PT, weekdays)
|
||||
30 6 * * 1-5 bash ~/clawd/skills/finance-news/cron/morning.sh
|
||||
|
||||
# Evening briefing (1:00 PM PT, weekdays)
|
||||
0 13 * * 1-5 bash ~/clawd/skills/finance-news/cron/evening.sh
|
||||
```
|
||||
|
||||
## Sample Output
|
||||
|
||||
```markdown
|
||||
🌅 **Börsen-Morgen-Briefing**
|
||||
Dienstag, 21. Januar 2026 | 06:30 Uhr
|
||||
|
||||
📊 **Märkte**
|
||||
• S&P 500: 5.234 (+0,3%)
|
||||
• DAX: 16.890 (-0,1%)
|
||||
• Nikkei: 35.678 (+0,5%)
|
||||
|
||||
📈 **Dein Portfolio**
|
||||
• AAPL $256 (+1,2%) — iPhone-Verkäufe übertreffen Erwartungen
|
||||
• NVDA $512 (+3,4%) — KI-Chip-Nachfrage steigt
|
||||
|
||||
🔥 **Top Stories**
|
||||
• [WSJ] Fed signalisiert mögliche Zinssenkung im März
|
||||
• [CNBC] Tech-Sektor führt Rally an
|
||||
|
||||
🤖 **Analyse**
|
||||
Der S&P zeigt Stärke. Dein Portfolio profitiert von NVDA's
|
||||
Momentum. Fed-Kommentare könnten Volatilität auslösen.
|
||||
```
|
||||
|
||||
## Integration
|
||||
|
||||
### With OpenBB (existing skill)
|
||||
```bash
|
||||
# Get detailed quote, then news
|
||||
openbb-quote AAPL && finance-news news AAPL
|
||||
```
|
||||
|
||||
### With OpenClaw Agent
|
||||
The agent will automatically use this skill when asked about:
|
||||
- "What's the market doing?"
|
||||
- "News for my portfolio"
|
||||
- "Generate morning briefing"
|
||||
- "What's happening with AAPL?"
|
||||
|
||||
### With Lobster (Workflow Engine)
|
||||
|
||||
Run briefings via [Lobster](https://github.com/openclaw/lobster) for approval gates and resumability:
|
||||
|
||||
```bash
|
||||
# Run with approval before WhatsApp send
|
||||
lobster "workflows.run --file workflows/briefing.yaml"
|
||||
|
||||
# With custom args
|
||||
lobster "workflows.run --file workflows/briefing.yaml --args-json '{\"time\":\"evening\",\"lang\":\"en\"}'"
|
||||
```
|
||||
|
||||
See `workflows/README.md` for full documentation.
|
||||
|
||||
## Files
|
||||
|
||||
```
|
||||
skills/finance-news/
|
||||
├── SKILL.md # This documentation
|
||||
├── Dockerfile # NixOS-compatible container
|
||||
├── config/
|
||||
│ ├── portfolio.csv # Your watchlist
|
||||
│ ├── config.json # RSS/API/language configuration
|
||||
│ ├── alerts.json # Price target alerts
|
||||
│ └── manual_earnings.json # Earnings calendar overrides
|
||||
├── scripts/
|
||||
│ ├── finance-news # Main CLI
|
||||
│ ├── briefing.py # Briefing generator
|
||||
│ ├── fetch_news.py # News aggregator
|
||||
│ ├── portfolio.py # Portfolio CRUD
|
||||
│ ├── summarize.py # AI summarization
|
||||
│ ├── alerts.py # Price alert management
|
||||
│ ├── earnings.py # Earnings calendar
|
||||
│ ├── ranking.py # Headline ranking
|
||||
│ └── stocks.py # Stock management
|
||||
├── workflows/
|
||||
│ ├── briefing.yaml # Lobster workflow with approval gate
|
||||
│ └── README.md # Workflow documentation
|
||||
├── cron/
|
||||
│ ├── morning.sh # Morning cron (Docker-based)
|
||||
│ └── evening.sh # Evening cron (Docker-based)
|
||||
└── cache/ # 15-minute news cache
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Python 3.10+
|
||||
- `feedparser` (`pip install feedparser`)
|
||||
- Gemini CLI (`brew install gemini-cli`)
|
||||
- OpenBB (existing `openbb-quote` wrapper)
|
||||
- OpenClaw message tool (for WhatsApp delivery)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Gemini not working
|
||||
```bash
|
||||
# Authenticate Gemini
|
||||
gemini # Follow login flow
|
||||
```
|
||||
|
||||
### RSS feeds timing out
|
||||
- Check network connectivity
|
||||
- WSJ/Barron's may require subscription cookies for some content
|
||||
- Free feeds (CNBC, Yahoo) should always work
|
||||
|
||||
### WhatsApp delivery failing
|
||||
- Verify WhatsApp group exists and bot has access
|
||||
- Check `openclaw doctor` for WhatsApp status
|
||||
6
_meta.json
Normal file
6
_meta.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"ownerId": "kn7fmw4ybcy50qzp1d2dvb1h517znaes",
|
||||
"slug": "finance-news",
|
||||
"version": "1.0.1",
|
||||
"publishedAt": 1770017717268
|
||||
}
|
||||
242
config/config.json
Normal file
242
config/config.json
Normal file
@@ -0,0 +1,242 @@
|
||||
{
|
||||
"rss_feeds": {
|
||||
"wsj": {
|
||||
"name": "Wall Street Journal",
|
||||
"enabled": true,
|
||||
"markets": "https://feeds.content.dowjones.io/public/rss/RSSMarketsMain",
|
||||
"daily": "https://feeds.content.dowjones.io/public/rss/RSSWSJD"
|
||||
},
|
||||
"tagesschau": {
|
||||
"name": "Tagesschau",
|
||||
"enabled": true,
|
||||
"wirtschaft": "https://www.tagesschau.de/wirtschaft/weltwirtschaft/index~rss2.xml"
|
||||
},
|
||||
"finanzen_net": {
|
||||
"name": "Finanzen.net",
|
||||
"enabled": true,
|
||||
"news": "https://www.finanzen.net/rss/news"
|
||||
},
|
||||
"handelsblatt": {
|
||||
"name": "Handelsblatt",
|
||||
"enabled": true,
|
||||
"finanzen": "https://feeds.cms.handelsblatt.com/finanzen"
|
||||
},
|
||||
"zeit": {
|
||||
"name": "ZEIT Wirtschaft",
|
||||
"enabled": true,
|
||||
"wirtschaft": "https://newsfeed.zeit.de/wirtschaft/index"
|
||||
},
|
||||
"marketwatch": {
|
||||
"name": "MarketWatch",
|
||||
"enabled": true,
|
||||
"topstories": "https://feeds.content.dowjones.io/public/rss/mw_topstories"
|
||||
},
|
||||
"reuters": {
|
||||
"name": "Reuters",
|
||||
"enabled": true,
|
||||
"markets": "https://news.google.com/rss/search?q=site%3Areuters.com+markets+OR+stocks+OR+economy+OR+fed+OR+earnings&hl=en-US&gl=US&ceid=US%3Aen",
|
||||
"note": "Google News RSS wrapper for Reuters - filtered for finance/markets."
|
||||
},
|
||||
"ft": {
|
||||
"name": "Financial Times",
|
||||
"enabled": true,
|
||||
"markets": "https://www.ft.com/markets?format=rss"
|
||||
},
|
||||
"bloomberg": {
|
||||
"name": "Bloomberg",
|
||||
"enabled": true,
|
||||
"markets": "https://feeds.bloomberg.com/markets/news.rss"
|
||||
},
|
||||
"barrons": {
|
||||
"name": "Barron's",
|
||||
"enabled": false,
|
||||
"main": "https://www.barrons.com/market-data/rss/articles",
|
||||
"note": "Requires subscription - enable after adding credentials"
|
||||
},
|
||||
"cnbc": {
|
||||
"name": "CNBC",
|
||||
"enabled": true,
|
||||
"top": "https://search.cnbc.com/rs/search/combinedcms/view.xml?partnerId=wrss01&id=10001147",
|
||||
"business": "https://search.cnbc.com/rs/search/combinedcms/view.xml?partnerId=wrss01&id=15839069",
|
||||
"markets": "https://search.cnbc.com/rs/search/combinedcms/view.xml?partnerId=wrss01&id=20910258",
|
||||
"world": "https://search.cnbc.com/rs/search/combinedcms/view.xml?partnerId=wrss01&id=10000664",
|
||||
"tech": "https://www.cnbc.com/id/19854910/device/rss/rss.html"
|
||||
},
|
||||
"yahoo": {
|
||||
"name": "Yahoo Finance",
|
||||
"enabled": true,
|
||||
"top": "https://finance.yahoo.com/rss/topstories"
|
||||
}
|
||||
},
|
||||
"headline_sources": ["reuters", "wsj", "ft", "bloomberg", "marketwatch", "cnbc", "yahoo"],
|
||||
"headline_sources_by_lang": {
|
||||
"de": ["tagesschau", "handelsblatt", "zeit", "finanzen_net", "reuters", "wsj", "ft", "bloomberg", "marketwatch", "cnbc", "yahoo"],
|
||||
"en": ["reuters", "wsj", "ft", "bloomberg", "marketwatch", "cnbc", "yahoo"]
|
||||
},
|
||||
"headline_exclude": [],
|
||||
"source_weights": {
|
||||
"reuters": 5,
|
||||
"wsj": 4,
|
||||
"ft": 4,
|
||||
"bloomberg": 3,
|
||||
"marketwatch": 3,
|
||||
"cnbc": 2,
|
||||
"tagesschau": 4,
|
||||
"handelsblatt": 4,
|
||||
"zeit": 4,
|
||||
"finanzen_net": 3,
|
||||
"yahoo": 1
|
||||
},
|
||||
"source_tiers": {
|
||||
"paid": ["wsj", "ft", "barrons"],
|
||||
"free": ["bloomberg", "marketwatch", "yahoo", "cnbc", "tagesschau", "handelsblatt", "zeit", "finanzen_net"]
|
||||
},
|
||||
"headline_shortlist_size_by_lang": {
|
||||
"de": 30,
|
||||
"en": 20
|
||||
},
|
||||
"portfolio_deadline_sec": 360,
|
||||
"portfolio": {
|
||||
"briefing_limit": 10,
|
||||
"prioritization_enabled": true,
|
||||
"prioritization_weights": {
|
||||
"type": 0.40,
|
||||
"volatility": 0.35,
|
||||
"news_volume": 0.25
|
||||
}
|
||||
},
|
||||
"markets": {
|
||||
"us": {
|
||||
"name": "US Markets",
|
||||
"enabled": true,
|
||||
"indices": ["^GSPC", "^DJI", "^IXIC"],
|
||||
"index_names": {"^GSPC": "S&P 500", "^DJI": "Dow Jones", "^IXIC": "NASDAQ"}
|
||||
},
|
||||
"europe": {
|
||||
"name": "Europe",
|
||||
"enabled": true,
|
||||
"indices": ["^GDAXI", "^STOXX50E", "^FTSE"],
|
||||
"index_names": {"^GDAXI": "DAX", "^STOXX50E": "STOXX 50", "^FTSE": "FTSE 100"}
|
||||
},
|
||||
"japan": {
|
||||
"name": "Japan",
|
||||
"enabled": true,
|
||||
"indices": ["^N225"],
|
||||
"index_names": {"^N225": "Nikkei 225"}
|
||||
}
|
||||
},
|
||||
"language": {
|
||||
"default": "en",
|
||||
"supported": ["en", "de"]
|
||||
},
|
||||
"delivery": {
|
||||
"whatsapp": {
|
||||
"enabled": true,
|
||||
"group": ""
|
||||
},
|
||||
"telegram": {
|
||||
"enabled": false,
|
||||
"group": ""
|
||||
}
|
||||
},
|
||||
"schedule": {
|
||||
"morning": {
|
||||
"enabled": true,
|
||||
"cron": "30 6 * * 1-5",
|
||||
"timezone": "America/Los_Angeles",
|
||||
"description": "US Market Open (9:30 AM ET = 6:30 AM PT)"
|
||||
},
|
||||
"evening": {
|
||||
"enabled": true,
|
||||
"cron": "0 13 * * 1-5",
|
||||
"timezone": "America/Los_Angeles",
|
||||
"description": "US Market Close (4:00 PM ET = 1:00 PM PT)"
|
||||
}
|
||||
},
|
||||
"llm": {
|
||||
"headline_model_order": ["gemini", "minimax", "claude"],
|
||||
"summary_model_order": ["gemini", "minimax", "claude"],
|
||||
"translation_model_order": ["gemini", "minimax", "claude"]
|
||||
},
|
||||
"translations": {
|
||||
"en": {
|
||||
"title_morning": "Morning Briefing",
|
||||
"title_evening": "Evening Briefing",
|
||||
"title_prefix": "Market",
|
||||
"time_suffix": "",
|
||||
"heading_briefing": "Market Briefing",
|
||||
"heading_markets": "Markets",
|
||||
"heading_sentiment": "Sentiment",
|
||||
"heading_top_headlines": "Top 5 Headlines",
|
||||
"heading_portfolio_impact": "Portfolio Impact",
|
||||
"heading_portfolio_movers": "Portfolio Movers",
|
||||
"heading_watchpoints": "Watchpoints",
|
||||
"no_data": "No data available",
|
||||
"no_movers": "No significant moves (±1%)",
|
||||
"follows_market": " -- follows market",
|
||||
"no_catalyst": " -- no specific catalyst",
|
||||
"rec_bullish": "Selective opportunities, keep risk management tight.",
|
||||
"rec_bearish": "Reduce risk and prioritize liquidity.",
|
||||
"rec_neutral": "Wait-and-see, focus on quality names.",
|
||||
"rec_unknown": "No clear recommendation without reliable data.",
|
||||
"sources_header": "Sources",
|
||||
"sentiment_map": {
|
||||
"Bullish": "Bullish",
|
||||
"Bearish": "Bearish",
|
||||
"Neutral": "Neutral",
|
||||
"No data available": "No data available"
|
||||
}
|
||||
},
|
||||
"de": {
|
||||
"title_morning": "Morgen-Briefing",
|
||||
"title_evening": "Abend-Briefing",
|
||||
"title_prefix": "Börsen",
|
||||
"time_suffix": "Uhr",
|
||||
"heading_briefing": "Marktbriefing",
|
||||
"heading_markets": "Märkte",
|
||||
"heading_sentiment": "Stimmung",
|
||||
"heading_top_headlines": "Top 5 Schlagzeilen",
|
||||
"heading_portfolio_impact": "Portfolio-Auswirkung",
|
||||
"heading_portfolio_movers": "Portfolio-Bewegungen",
|
||||
"heading_watchpoints": "Beobachtungspunkte",
|
||||
"no_data": "Keine Daten verfügbar",
|
||||
"no_movers": "Keine deutlichen Bewegungen (±1%)",
|
||||
"follows_market": " -- folgt dem Markt",
|
||||
"no_catalyst": " -- kein spezifischer Katalysator",
|
||||
"rec_bullish": "Chancen selektiv nutzen, aber Risikomanagement beibehalten.",
|
||||
"rec_bearish": "Risiken reduzieren und Liquidität priorisieren.",
|
||||
"rec_neutral": "Abwarten und Fokus auf Qualitätstitel.",
|
||||
"rec_unknown": "Keine klare Empfehlung ohne belastbare Daten.",
|
||||
"sources_header": "Quellen",
|
||||
"sentiment_map": {
|
||||
"Bullish": "Bullisch",
|
||||
"Bearish": "Bärisch",
|
||||
"Neutral": "Neutral",
|
||||
"No data available": "Keine Daten verfügbar"
|
||||
},
|
||||
"months": {
|
||||
"January": "Januar",
|
||||
"February": "Februar",
|
||||
"March": "März",
|
||||
"April": "April",
|
||||
"May": "Mai",
|
||||
"June": "Juni",
|
||||
"July": "Juli",
|
||||
"August": "August",
|
||||
"September": "September",
|
||||
"October": "Oktober",
|
||||
"November": "November",
|
||||
"December": "Dezember"
|
||||
},
|
||||
"days": {
|
||||
"Monday": "Montag",
|
||||
"Tuesday": "Dienstag",
|
||||
"Wednesday": "Mittwoch",
|
||||
"Thursday": "Donnerstag",
|
||||
"Friday": "Freitag",
|
||||
"Saturday": "Samstag",
|
||||
"Sunday": "Sonntag"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
327
config/manual_earnings.json
Normal file
327
config/manual_earnings.json
Normal file
@@ -0,0 +1,327 @@
|
||||
{
|
||||
"_comment": "Manual earnings dates for stocks not covered by Finnhub API",
|
||||
"_updated": "2026-01-27",
|
||||
"6857.T": {
|
||||
"date": "2026-01-27",
|
||||
"time": "amc",
|
||||
"note": "Q3 FY2025 - Advantest",
|
||||
"source": "marketscreener.com"
|
||||
},
|
||||
"6920.T": {
|
||||
"date": "2026-02-02",
|
||||
"time": "amc",
|
||||
"note": "Q3 FY2025 - Lasertec",
|
||||
"source": "tipranks.com"
|
||||
},
|
||||
"8035.T": {
|
||||
"date": "2026-02-05",
|
||||
"time": "amc",
|
||||
"note": "Q3 FY2025 - Tokyo Electron",
|
||||
"source": "tipranks.com"
|
||||
},
|
||||
"6146.T": {
|
||||
"date": "2026-02-06",
|
||||
"time": "amc",
|
||||
"note": "Q3 FY2025 - Disco Corp",
|
||||
"source": "estimate"
|
||||
},
|
||||
"7741.T": {
|
||||
"date": "2026-01-30",
|
||||
"time": "amc",
|
||||
"note": "Q3 FY2025 - Hoya",
|
||||
"source": "estimate"
|
||||
},
|
||||
"7735.T": {
|
||||
"date": "2026-01-30",
|
||||
"time": "amc",
|
||||
"note": "Q3 FY2025 - Screen Holdings",
|
||||
"source": "estimate"
|
||||
},
|
||||
"4063.T": {
|
||||
"date": "2026-01-31",
|
||||
"time": "amc",
|
||||
"note": "Q3 FY2025 - Shin-Etsu Chemical",
|
||||
"source": "estimate"
|
||||
},
|
||||
"6861.T": {
|
||||
"date": "2026-01-29",
|
||||
"time": "amc",
|
||||
"note": "Q3 FY2025 - Keyence",
|
||||
"source": "estimate"
|
||||
},
|
||||
"9984.T": {
|
||||
"date": "2026-02-07",
|
||||
"time": "amc",
|
||||
"note": "Q3 FY2025 - SoftBank Group",
|
||||
"source": "estimate"
|
||||
},
|
||||
"9983.T": {
|
||||
"date": "2026-01-09",
|
||||
"time": "amc",
|
||||
"note": "Q1 FY2026 - Fast Retailing (Uniqlo)",
|
||||
"source": "estimate"
|
||||
},
|
||||
"D05.SI": {
|
||||
"date": "2026-02-10",
|
||||
"time": "bmo",
|
||||
"note": "Q4 2025 - DBS Group",
|
||||
"source": "estimate"
|
||||
},
|
||||
"O39.SI": {
|
||||
"date": "2026-02-21",
|
||||
"time": "bmo",
|
||||
"note": "Q4 2025 - OCBC Bank",
|
||||
"source": "estimate"
|
||||
},
|
||||
"S68.SI": {
|
||||
"date": "2026-01-23",
|
||||
"time": "bmo",
|
||||
"note": "H1 FY2026 - Singapore Exchange",
|
||||
"source": "estimate"
|
||||
},
|
||||
"AAPL": {
|
||||
"date": "2026-01-30",
|
||||
"time": "amc",
|
||||
"note": "Q1 FY2026"
|
||||
},
|
||||
"MSFT": {
|
||||
"date": "2026-01-29",
|
||||
"time": "amc",
|
||||
"note": "Q2 FY2026"
|
||||
},
|
||||
"META": {
|
||||
"date": "2026-01-29",
|
||||
"time": "amc",
|
||||
"note": "Q4 2025"
|
||||
},
|
||||
"TSLA": {
|
||||
"date": "2026-01-29",
|
||||
"time": "amc",
|
||||
"note": "Q4 2025"
|
||||
},
|
||||
"NVDA": {
|
||||
"date": "2026-02-25",
|
||||
"time": "amc",
|
||||
"note": "Q4 FY2026"
|
||||
},
|
||||
"GOOGL": {
|
||||
"date": "2026-02-04",
|
||||
"time": "amc",
|
||||
"note": "Q4 2025"
|
||||
},
|
||||
"AMZN": {
|
||||
"date": "2026-02-06",
|
||||
"time": "amc",
|
||||
"note": "Q4 2025"
|
||||
},
|
||||
"NFLX": {
|
||||
"date": "2026-01-21",
|
||||
"time": "amc",
|
||||
"note": "Q4 2025"
|
||||
},
|
||||
"V": {
|
||||
"date": "2026-01-30",
|
||||
"time": "amc",
|
||||
"note": "Q1 FY2026"
|
||||
},
|
||||
"MA": {
|
||||
"date": "2026-01-30",
|
||||
"time": "bmo",
|
||||
"note": "Q4 2025"
|
||||
},
|
||||
"ASML": {
|
||||
"date": "2026-01-29",
|
||||
"time": "bmo",
|
||||
"note": "Q4 2025"
|
||||
},
|
||||
"NOW": {
|
||||
"date": "2026-01-29",
|
||||
"time": "amc",
|
||||
"note": "Q4 2025"
|
||||
},
|
||||
"UBER": {
|
||||
"date": "2026-02-05",
|
||||
"time": "bmo",
|
||||
"note": "Q4 2025"
|
||||
},
|
||||
"SHOP": {
|
||||
"date": "2026-02-11",
|
||||
"time": "bmo",
|
||||
"note": "Q4 2025"
|
||||
},
|
||||
"SPOT": {
|
||||
"date": "2026-02-04",
|
||||
"time": "bmo",
|
||||
"note": "Q4 2025"
|
||||
},
|
||||
"NET": {
|
||||
"date": "2026-02-06",
|
||||
"time": "amc",
|
||||
"note": "Q4 2025"
|
||||
},
|
||||
"SNOW": {
|
||||
"date": "2026-02-26",
|
||||
"time": "amc",
|
||||
"note": "Q4 FY2026"
|
||||
},
|
||||
"DKNG": {
|
||||
"date": "2026-02-13",
|
||||
"time": "bmo",
|
||||
"note": "Q4 2025"
|
||||
},
|
||||
"SQ": {
|
||||
"date": "2026-02-20",
|
||||
"time": "amc",
|
||||
"note": "Q4 2025"
|
||||
},
|
||||
"ABNB": {
|
||||
"date": "2026-02-13",
|
||||
"time": "amc",
|
||||
"note": "Q4 2025"
|
||||
},
|
||||
"TEAM": {
|
||||
"date": "2026-01-30",
|
||||
"time": "amc",
|
||||
"note": "Q2 FY2026"
|
||||
},
|
||||
"ZS": {
|
||||
"date": "2026-02-25",
|
||||
"time": "amc",
|
||||
"note": "Q2 FY2026"
|
||||
},
|
||||
"FTNT": {
|
||||
"date": "2026-02-06",
|
||||
"time": "amc",
|
||||
"note": "Q4 2025"
|
||||
},
|
||||
"WDAY": {
|
||||
"date": "2026-02-27",
|
||||
"time": "amc",
|
||||
"note": "Q4 FY2026"
|
||||
},
|
||||
"TTD": {
|
||||
"date": "2026-02-13",
|
||||
"time": "amc",
|
||||
"note": "Q4 2025"
|
||||
},
|
||||
"WMT": {
|
||||
"date": "2026-02-19",
|
||||
"time": "bmo",
|
||||
"note": "Q4 FY2026"
|
||||
},
|
||||
"EA": {
|
||||
"date": "2026-02-03",
|
||||
"time": "amc",
|
||||
"note": "Q3 FY2026"
|
||||
},
|
||||
"ADSK": {
|
||||
"date": "2026-02-26",
|
||||
"time": "amc",
|
||||
"note": "Q4 FY2026"
|
||||
},
|
||||
"ROKU": {
|
||||
"date": "2026-02-13",
|
||||
"time": "amc",
|
||||
"note": "Q4 2025"
|
||||
},
|
||||
"SNAP": {
|
||||
"date": "2026-02-04",
|
||||
"time": "amc",
|
||||
"note": "Q4 2025"
|
||||
},
|
||||
"ETSY": {
|
||||
"date": "2026-02-19",
|
||||
"time": "amc",
|
||||
"note": "Q4 2025"
|
||||
},
|
||||
"KO": {
|
||||
"date": "2026-02-11",
|
||||
"time": "bmo",
|
||||
"note": "Q4 2025"
|
||||
},
|
||||
"BLK": {
|
||||
"date": "2026-01-15",
|
||||
"time": "bmo",
|
||||
"note": "Q4 2025"
|
||||
},
|
||||
"PH": {
|
||||
"date": "2026-01-30",
|
||||
"time": "bmo",
|
||||
"note": "Q2 FY2026"
|
||||
},
|
||||
"SYK": {
|
||||
"date": "2026-01-28",
|
||||
"time": "bmo",
|
||||
"note": "Q4 2025"
|
||||
},
|
||||
"TJX": {
|
||||
"date": "2026-02-26",
|
||||
"time": "bmo",
|
||||
"note": "Q4 FY2026"
|
||||
},
|
||||
"ROST": {
|
||||
"date": "2026-03-04",
|
||||
"time": "amc",
|
||||
"note": "Q4 FY2026"
|
||||
},
|
||||
"ORLY": {
|
||||
"date": "2026-02-05",
|
||||
"time": "amc",
|
||||
"note": "Q4 2025"
|
||||
},
|
||||
"SHW": {
|
||||
"date": "2026-01-30",
|
||||
"time": "bmo",
|
||||
"note": "Q4 2025"
|
||||
},
|
||||
"FISV": {
|
||||
"date": "2026-02-04",
|
||||
"time": "bmo",
|
||||
"note": "Q4 2025"
|
||||
},
|
||||
"MSI": {
|
||||
"date": "2026-02-06",
|
||||
"time": "bmo",
|
||||
"note": "Q4 2025"
|
||||
},
|
||||
"APH": {
|
||||
"date": "2026-01-22",
|
||||
"time": "bmo",
|
||||
"note": "Q4 2025"
|
||||
},
|
||||
"AXON": {
|
||||
"date": "2026-02-25",
|
||||
"time": "amc",
|
||||
"note": "Q4 2025"
|
||||
},
|
||||
"ROP": {
|
||||
"date": "2026-01-30",
|
||||
"time": "bmo",
|
||||
"note": "Q4 2025"
|
||||
},
|
||||
"RACE": {
|
||||
"date": "2026-02-04",
|
||||
"time": "bmo",
|
||||
"note": "Q4 2025"
|
||||
},
|
||||
"TWLO": {
|
||||
"date": "2026-02-12",
|
||||
"time": "amc",
|
||||
"note": "Q4 2025"
|
||||
},
|
||||
"ZM": {
|
||||
"date": "2026-02-24",
|
||||
"time": "amc",
|
||||
"note": "Q4 FY2026"
|
||||
},
|
||||
"U": {
|
||||
"date": "2026-02-20",
|
||||
"time": "amc",
|
||||
"note": "Q4 2025"
|
||||
},
|
||||
"ZI": {
|
||||
"date": "2026-02-10",
|
||||
"time": "amc",
|
||||
"note": "Q4 2025"
|
||||
}
|
||||
}
|
||||
19
cron/alerts.sh
Normal file
19
cron/alerts.sh
Normal file
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
# Price Alerts Cron Job (Lobster Workflow)
|
||||
# Schedule: 2:00 PM PT / 5:00 PM ET (1 hour after market close)
|
||||
#
|
||||
# Checks price alerts against current prices including after-hours.
|
||||
# Sends triggered alerts and watchlist status to WhatsApp/Telegram.
|
||||
|
||||
set -e
|
||||
|
||||
export SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
export FINANCE_NEWS_TARGET="${FINANCE_NEWS_TARGET:-120363421796203667@g.us}"
|
||||
export FINANCE_NEWS_CHANNEL="${FINANCE_NEWS_CHANNEL:-whatsapp}"
|
||||
|
||||
echo "[$(date)] Checking price alerts via Lobster..."
|
||||
|
||||
lobster run --file "$SKILL_DIR/workflows/alerts-cron.yaml" \
|
||||
--args-json '{"lang":"en"}'
|
||||
|
||||
echo "[$(date)] Price alerts check complete."
|
||||
19
cron/earnings-weekly.sh
Normal file
19
cron/earnings-weekly.sh
Normal file
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
# Weekly Earnings Alert Cron Job (Lobster Workflow)
|
||||
# Schedule: Sunday 7:00 AM PT (before market week starts)
|
||||
#
|
||||
# Sends upcoming week's earnings calendar to WhatsApp/Telegram.
|
||||
# Shows all portfolio stocks reporting Mon-Fri.
|
||||
|
||||
set -e
|
||||
|
||||
export SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
export FINANCE_NEWS_TARGET="${FINANCE_NEWS_TARGET:-120363421796203667@g.us}"
|
||||
export FINANCE_NEWS_CHANNEL="${FINANCE_NEWS_CHANNEL:-whatsapp}"
|
||||
|
||||
echo "[$(date)] Checking next week's earnings via Lobster..."
|
||||
|
||||
lobster run --file "$SKILL_DIR/workflows/earnings-weekly-cron.yaml" \
|
||||
--args-json '{"lang":"en"}'
|
||||
|
||||
echo "[$(date)] Weekly earnings alert complete."
|
||||
19
cron/earnings.sh
Normal file
19
cron/earnings.sh
Normal file
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
# Earnings Alert Cron Job (Lobster Workflow)
|
||||
# Schedule: 6:00 AM PT / 9:00 AM ET (30 min before market open)
|
||||
#
|
||||
# Sends today's earnings calendar to WhatsApp/Telegram.
|
||||
# Alerts users about portfolio stocks reporting today.
|
||||
|
||||
set -e
|
||||
|
||||
export SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
export FINANCE_NEWS_TARGET="${FINANCE_NEWS_TARGET:-120363421796203667@g.us}"
|
||||
export FINANCE_NEWS_CHANNEL="${FINANCE_NEWS_CHANNEL:-whatsapp}"
|
||||
|
||||
echo "[$(date)] Checking today's earnings via Lobster..."
|
||||
|
||||
lobster run --file "$SKILL_DIR/workflows/earnings-cron.yaml" \
|
||||
--args-json '{"lang":"en"}'
|
||||
|
||||
echo "[$(date)] Earnings alert complete."
|
||||
19
cron/evening.sh
Normal file
19
cron/evening.sh
Normal file
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
# Evening Briefing Cron Job (Lobster Workflow)
|
||||
# Schedule: 1:00 PM PT (US Market Close at 4:00 PM ET)
|
||||
#
|
||||
# Uses Lobster workflow to generate and send briefing directly,
|
||||
# bypassing LLM agent reformatting that truncates output.
|
||||
|
||||
set -e
|
||||
|
||||
export SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
export FINANCE_NEWS_TARGET="${FINANCE_NEWS_TARGET:-120363421796203667@g.us}"
|
||||
export FINANCE_NEWS_CHANNEL="${FINANCE_NEWS_CHANNEL:-whatsapp}"
|
||||
|
||||
echo "[$(date)] Starting evening briefing via Lobster..."
|
||||
|
||||
lobster run --file "$SKILL_DIR/workflows/briefing-cron.yaml" \
|
||||
--args-json '{"time":"evening","lang":"de"}'
|
||||
|
||||
echo "[$(date)] Evening briefing complete."
|
||||
19
cron/morning.sh
Normal file
19
cron/morning.sh
Normal file
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
# Morning Briefing Cron Job (Lobster Workflow)
|
||||
# Schedule: 6:30 AM PT (US Market Open at 9:30 AM ET)
|
||||
#
|
||||
# Uses Lobster workflow to generate and send briefing directly,
|
||||
# bypassing LLM agent reformatting that truncates output.
|
||||
|
||||
set -e
|
||||
|
||||
export SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
export FINANCE_NEWS_TARGET="${FINANCE_NEWS_TARGET:-120363421796203667@g.us}"
|
||||
export FINANCE_NEWS_CHANNEL="${FINANCE_NEWS_CHANNEL:-whatsapp}"
|
||||
|
||||
echo "[$(date)] Starting morning briefing via Lobster..."
|
||||
|
||||
lobster run --file "$SKILL_DIR/workflows/briefing-cron.yaml" \
|
||||
--args-json '{"time":"morning","lang":"de"}'
|
||||
|
||||
echo "[$(date)] Morning briefing complete."
|
||||
122
docs/EQUITY_SHEET_FIXES.md
Normal file
122
docs/EQUITY_SHEET_FIXES.md
Normal file
@@ -0,0 +1,122 @@
|
||||
# Equity Sheet Fixes
|
||||
|
||||
## Contents
|
||||
- [NRR Column Fix](#nrr-column-column-q---range-values-fix)
|
||||
- [Conversion Rules](#conversion-rules)
|
||||
- [Fix Procedure](#fix-procedure)
|
||||
- [Impact](#impact)
|
||||
- [Related Columns](#related-columns)
|
||||
- [Prevention](#prevention)
|
||||
|
||||
## NRR Column (Column Q) - Range Values Fix
|
||||
|
||||
**Problem:** Values like "115-120%", "125%+", "N/A" in NRR column cause #VALUE! errors in MSS Score formula (columns Y/Z).
|
||||
|
||||
**Root cause:** Excel/Sheets formulas cannot perform math operations on text ranges.
|
||||
|
||||
**Solution:** Convert all NRR values to single numeric percentages.
|
||||
|
||||
### Conversion Rules
|
||||
|
||||
**Standard formats:**
|
||||
|
||||
| Original | Fixed | Calculation | Rationale |
|
||||
|----------|-------|-------------|-----------|
|
||||
| 115-120% | 117.5% | (115+120)/2 | Midpoint (conservative estimate) |
|
||||
| 120-125% | 122.5% | (120+125)/2 | Midpoint |
|
||||
| 125%+ | 125% | Use lower bound | Conservative (actual may be higher) |
|
||||
| N/A | [blank] | Leave empty | MSS formula uses IFERROR to handle blanks |
|
||||
| 110% | 110% | Already valid | No change needed |
|
||||
|
||||
**Edge cases (normalize before converting):**
|
||||
|
||||
| Variant | Normalized | Notes |
|
||||
|---------|------------|-------|
|
||||
| 115–120% (en-dash) | 115-120% | Replace en-dash with hyphen |
|
||||
| 115 - 120% (spaces) | 115-120% | Remove spaces around hyphen |
|
||||
| >=125% | 125%+ | Convert to standard "+" format |
|
||||
| 125%+ or higher | 125%+ | Strip extra text |
|
||||
|
||||
### Fix Procedure
|
||||
|
||||
**Option A: Manual fix via browser**
|
||||
1. Open sheet: https://docs.google.com/spreadsheets/d/1lTpdbDjqW40qe4YUvk_1vBzKYLUNrmLZYyQN-7HmFJg/edit#gid=0
|
||||
2. **IMPORTANT:** Select column Q header → Format → Number → Percent
|
||||
- This ensures values are stored as numbers, not text
|
||||
- If column is set to "Plain text", entering "117.5%" stores as text → still causes errors
|
||||
3. Navigate to column Q (NRR)
|
||||
4. For each range value:
|
||||
- Calculate midpoint (e.g., (115+120)/2 = 117.5)
|
||||
- Replace with single percentage: `117.5%`
|
||||
- Sheets auto-converts to numeric percentage when column is formatted correctly
|
||||
5. For "N/A" → delete content (leave blank)
|
||||
6. For "125%+" → replace with `125%`
|
||||
7. **Verify:** After editing, click cell → formula bar should show `1.175` (not `"117.5%"` with quotes)
|
||||
|
||||
**Option B: Sheets API fix (requires Sheets API enabled)**
|
||||
|
||||
**Prerequisites:**
|
||||
1. Enable Sheets API: https://console.developers.google.com/apis/api/sheets.googleapis.com/overview?project=831892255935
|
||||
2. Ensure column Q is formatted as Percent (do once before any API writes):
|
||||
- Via browser: Select column Q → Format → Number → Percent
|
||||
- Via API: Use `batchUpdate` with `repeatCell` + `numberFormat` (see below)
|
||||
|
||||
**Using gog CLI:**
|
||||
```bash
|
||||
# gog CLI uses USER_ENTERED by default (parses "117.5%" as numeric)
|
||||
gog-shapescale --account martin@shapescale.com sheets update \
|
||||
1lTpdbDjqW40qe4YUvk_1vBzKYLUNrmLZYyQN-7HmFJg \
|
||||
'Equity!Q5' '117.5%'
|
||||
```
|
||||
|
||||
**Using Sheets API directly (curl/Python):**
|
||||
```bash
|
||||
# CRITICAL: Specify valueInputOption=USER_ENTERED explicitly
|
||||
curl -X PUT \
|
||||
"https://sheets.googleapis.com/v4/spreadsheets/SHEET_ID/values/Equity!Q5?valueInputOption=USER_ENTERED" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-d '{"values": [["117.5%"]]}'
|
||||
|
||||
# Python example:
|
||||
service.spreadsheets().values().update(
|
||||
spreadsheetId=SHEET_ID,
|
||||
range='Equity!Q5',
|
||||
valueInputOption='USER_ENTERED', # Parse as Sheets would
|
||||
body={'values': [['117.5%']]}
|
||||
).execute()
|
||||
```
|
||||
|
||||
**Verify after writing:**
|
||||
- Click cell → formula bar should show `1.175` (numeric)
|
||||
- If formula bar shows `"117.5%"` with quotes → stored as text, still causes errors
|
||||
|
||||
### Impact
|
||||
|
||||
Fixing NRR ranges will:
|
||||
- ✅ Eliminate #VALUE! errors in MSS Score column (Y)
|
||||
- ✅ Eliminate #VALUE! errors in MSS Rating column (Z)
|
||||
- ✅ Allow proper numerical analysis and sorting
|
||||
- ✅ Make formulas copyable to new rows without errors
|
||||
|
||||
### How MSS Formula Handles Blank NRR Values
|
||||
|
||||
The MSS Score formula (column Y) includes `IFERROR()` wrapper to handle missing data:
|
||||
- **Blank NRR cell** → Formula treats as missing data, uses available metrics only
|
||||
- **Not treated as 0%** → Blank is excluded from calculation (doesn't penalize score)
|
||||
- **Better than text "N/A"** → Text causes #VALUE! error, blank is handled gracefully
|
||||
|
||||
**Example:** If NRR is blank but other metrics exist (Rev Growth, Rule of 40, etc.), MSS Score calculates using remaining metrics without error.
|
||||
|
||||
### Related Columns
|
||||
|
||||
Other columns that need single numeric values (not ranges):
|
||||
- **Column M (Rule of 40 Ops)**: Should be calculated value (Ops Margin + Rev Growth)
|
||||
- **Column O (Rule of 40 FCF)**: Should be calculated value (FCF Margin + Rev Growth)
|
||||
- Both can be negative for pre-profitable/turnaround companies
|
||||
|
||||
### Prevention
|
||||
|
||||
When adding new companies:
|
||||
1. Always use single percentage values in NRR column
|
||||
2. Test MSS Score formula immediately after adding row
|
||||
3. If #VALUE! error appears → check Q column for ranges/text
|
||||
212
docs/PREMIUM_SOURCES.md
Normal file
212
docs/PREMIUM_SOURCES.md
Normal file
@@ -0,0 +1,212 @@
|
||||
# Premium Source Authentication
|
||||
|
||||
## Contents
|
||||
- [Overview](#overview)
|
||||
- [Option 1: Keep It Simple (Recommended)](#option-1-keep-it-simple-recommended)
|
||||
- [Option 2: Use Premium Sources (Advanced)](#option-2-use-premium-sources-advanced)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
- [Alternative: Use APIs Instead](#alternative-use-apis-instead)
|
||||
- [Recommendation](#recommendation)
|
||||
|
||||
## Overview
|
||||
|
||||
WSJ and Barron's are premium financial news sources that require subscriptions. This guide explains how to authenticate and use premium sources with the finance-news skill.
|
||||
|
||||
**Recommendation:** For simplicity, we recommend using **free sources only** (Yahoo Finance, CNBC, MarketWatch). Premium sources add complexity and maintenance burden.
|
||||
|
||||
If you have subscriptions and want premium content, follow the steps below.
|
||||
|
||||
---
|
||||
|
||||
## Option 1: Keep It Simple (Recommended)
|
||||
|
||||
**Use free sources only.** They provide 90% of the value without authentication complexity:
|
||||
|
||||
- ✅ Yahoo Finance (free, reliable)
|
||||
- ✅ CNBC (free, real-time news)
|
||||
- ✅ MarketWatch (free, broad coverage)
|
||||
- ✅ Reuters (free via Yahoo RSS)
|
||||
|
||||
**To disable premium sources:**
|
||||
1. Edit `config/config.json` (legacy: `config/sources.json`)
|
||||
2. Set `"enabled": false` for WSJ/Barron's entries
|
||||
3. Done - no authentication needed
|
||||
|
||||
---
|
||||
|
||||
## Option 2: Use Premium Sources (Advanced)
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Active WSJ or Barron's subscription
|
||||
- Browser with active login session (Chrome/Firefox)
|
||||
- **Option B only:** Install `requests` library if needed:
|
||||
```bash
|
||||
pip install requests
|
||||
```
|
||||
|
||||
### Step 1: Export Cookies from Browser
|
||||
|
||||
**Chrome:**
|
||||
1. Install extension: [EditThisCookie](https://chrome.google.com/webstore/detail/editthiscookie/)
|
||||
2. Navigate to wsj.com (logged in)
|
||||
3. Click EditThisCookie icon → Export → Copy JSON
|
||||
|
||||
**Firefox:**
|
||||
1. Install extension: [Cookie Quick Manager](https://addons.mozilla.org/en-US/firefox/addon/cookie-quick-manager/)
|
||||
2. Navigate to wsj.com (logged in)
|
||||
3. Right-click page → Inspect → Storage → Cookies
|
||||
4. Copy relevant cookies (see format below)
|
||||
|
||||
### Step 2: Create Cookie File
|
||||
|
||||
Create `config/cookies.json` (this file is gitignored):
|
||||
|
||||
```json
|
||||
{
|
||||
"feeds.a.dj.com": {
|
||||
"wsjgeo": "US",
|
||||
"djcs_session": "YOUR_SESSION_TOKEN_HERE",
|
||||
"djcs_route": "YOUR_ROUTE_HERE"
|
||||
},
|
||||
"www.barrons.com": {
|
||||
"wsjgeo": "US",
|
||||
"djcs_session": "YOUR_SESSION_TOKEN_HERE"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Important:** Cookie domain must match feed URL domain:
|
||||
- WSJ feeds use `feeds.a.dj.com` (not `wsj.com`)
|
||||
- Barron's feeds use `www.barrons.com`
|
||||
- Check `config/config.json` for actual feed URLs
|
||||
|
||||
**Note:** Cookie names/values vary by site. Export from browser to get actual values.
|
||||
|
||||
### Step 3: Pass Cookies to fetch_news.py
|
||||
|
||||
**Option A: Modify fetch_news.py (not officially supported)**
|
||||
|
||||
Add cookie loading to `fetch_rss()` function (maintains existing signature):
|
||||
|
||||
```python
|
||||
import json
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
from urllib.parse import urlparse
|
||||
|
||||
def fetch_rss(url: str, limit: int = 10) -> list[dict]:
|
||||
"""Fetch and parse RSS feed with optional cookie authentication."""
|
||||
|
||||
# Load cookies if they exist
|
||||
cookie_file = Path(__file__).parent.parent / "config" / "cookies.json"
|
||||
cookies = {}
|
||||
if cookie_file.exists():
|
||||
with open(cookie_file) as f:
|
||||
all_cookies = json.load(f)
|
||||
# Extract domain from URL (e.g., feeds.a.dj.com)
|
||||
domain = urlparse(url).netloc
|
||||
cookies = all_cookies.get(domain, {})
|
||||
|
||||
# Fetch with cookies and User-Agent
|
||||
req = urllib.request.Request(url, headers={'User-Agent': 'OpenClaw/1.0'})
|
||||
if cookies:
|
||||
cookie_header = "; ".join([f"{k}={v}" for k, v in cookies.items()])
|
||||
req.add_header("Cookie", cookie_header)
|
||||
|
||||
# ... rest of function (unchanged)
|
||||
```
|
||||
|
||||
**Note:** This is a doc-only suggestion, not officially supported by the skill.
|
||||
|
||||
**Option B: Use requests library instead of urllib**
|
||||
|
||||
Replace `urllib` with `requests` for easier cookie handling (maintains API signature):
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
def fetch_rss(url: str, limit: int = 10, cookies_dict: dict = None) -> list[dict]:
|
||||
response = requests.get(url, cookies=cookies_dict, timeout=10)
|
||||
response.raise_for_status()
|
||||
# ... parse with feedparser
|
||||
```
|
||||
|
||||
### Step 4: Security Considerations
|
||||
|
||||
**Critical: Do NOT commit cookies to git**
|
||||
|
||||
1. **`.gitignore` already includes cookie files:**
|
||||
- `config/cookies.json`
|
||||
- `*.cookie`
|
||||
- No action needed (already configured)
|
||||
|
||||
2. **Set restrictive file permissions:**
|
||||
```bash
|
||||
chmod 600 config/cookies.json
|
||||
```
|
||||
|
||||
2. **Set restrictive file permissions:**
|
||||
```bash
|
||||
chmod 600 config/cookies.json
|
||||
```
|
||||
|
||||
3. **Rotate cookies regularly:**
|
||||
- Browser session cookies expire (usually 7-30 days)
|
||||
- Re-export cookies when authentication fails
|
||||
|
||||
4. **Never share cookie files:**
|
||||
- Cookies grant full account access
|
||||
- Treat like passwords
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "HTTP 403 Forbidden" errors
|
||||
|
||||
**Cause:** Cookies expired or invalid
|
||||
|
||||
**Fix:**
|
||||
1. Log in to WSJ/Barron's in browser
|
||||
2. Re-export cookies
|
||||
3. Update `config/cookies.json`
|
||||
|
||||
### "Paywall detected" in articles
|
||||
|
||||
**Cause:** RSS feed doesn't require auth, but full article does
|
||||
|
||||
**Fix:**
|
||||
- Premium sources often provide headlines/snippets in RSS (no auth needed)
|
||||
- Full articles require subscription + cookie auth
|
||||
- If you only need headlines → no cookies needed
|
||||
|
||||
### Cookies not working
|
||||
|
||||
**Debug checklist:**
|
||||
- [ ] Correct domain in cookies.json:
|
||||
- WSJ: Use `feeds.a.dj.com` (not `wsj.com`)
|
||||
- Barron's: Use `www.barrons.com` (not `barrons.com`)
|
||||
- Check `config/config.json` for actual feed URLs
|
||||
- [ ] Cookie values copied completely (no truncation)
|
||||
- [ ] Browser session still active (test by visiting site)
|
||||
- [ ] File permissions correct (chmod 600)
|
||||
|
||||
---
|
||||
|
||||
## Alternative: Use APIs Instead
|
||||
|
||||
Some premium sources offer APIs:
|
||||
- **WSJ API:** Not publicly available
|
||||
- **Barron's API:** Part of Dow Jones API (enterprise only)
|
||||
- **Bloomberg API:** Enterprise only
|
||||
|
||||
**Conclusion:** Cookie-based auth is the only practical option for individual users.
|
||||
|
||||
---
|
||||
|
||||
## Recommendation
|
||||
|
||||
**For most users:** Stick with free sources. They're reliable, no auth needed, and provide comprehensive market coverage.
|
||||
|
||||
**For premium subscribers:** Follow Option 2, but be prepared to maintain cookie files and handle expiration.
|
||||
281
htmlcov/class_index.html
generated
Normal file
281
htmlcov/class_index.html
generated
Normal file
@@ -0,0 +1,281 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>Coverage report</title>
|
||||
<link rel="icon" sizes="32x32" href="favicon_32_cb_c827f16f.png">
|
||||
<link rel="stylesheet" href="style_cb_9ff733b0.css" type="text/css">
|
||||
<script src="coverage_html_cb_dd2e7eb5.js" defer></script>
|
||||
</head>
|
||||
<body class="indexfile">
|
||||
<header>
|
||||
<div class="content">
|
||||
<h1>Coverage report:
|
||||
<span class="pc_cov">48%</span>
|
||||
</h1>
|
||||
<aside id="help_panel_wrapper">
|
||||
<input id="help_panel_state" type="checkbox">
|
||||
<label for="help_panel_state">
|
||||
<img id="keyboard_icon" src="keybd_closed_cb_900cfef5.png" alt="Show/hide keyboard shortcuts">
|
||||
</label>
|
||||
<div id="help_panel">
|
||||
<p class="legend">Shortcuts on this page</p>
|
||||
<div class="keyhelp">
|
||||
<p>
|
||||
<kbd>f</kbd>
|
||||
<kbd>n</kbd>
|
||||
<kbd>s</kbd>
|
||||
<kbd>m</kbd>
|
||||
<kbd>x</kbd>
|
||||
<kbd>c</kbd>
|
||||
change column sorting
|
||||
</p>
|
||||
<p>
|
||||
<kbd>[</kbd>
|
||||
<kbd>]</kbd>
|
||||
prev/next file
|
||||
</p>
|
||||
<p>
|
||||
<kbd>?</kbd> show/hide this help
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
<form id="filter_container">
|
||||
<input id="filter" type="text" value="" placeholder="filter...">
|
||||
<div>
|
||||
<input id="hide100" type="checkbox" >
|
||||
<label for="hide100">hide covered</label>
|
||||
</div>
|
||||
</form>
|
||||
<h2>
|
||||
<a class="button" href="index.html">Files</a>
|
||||
<a class="button" href="function_index.html">Functions</a>
|
||||
<a class="button current">Classes</a>
|
||||
</h2>
|
||||
<p class="text">
|
||||
<a class="nav" href="https://coverage.readthedocs.io/en/7.13.2">coverage.py v7.13.2</a>,
|
||||
created at 2026-02-01 16:34 -0800
|
||||
</p>
|
||||
</div>
|
||||
</header>
|
||||
<main id="index">
|
||||
<table class="index" data-sortable>
|
||||
<thead>
|
||||
<tr class="tablehead" title="Click to sort">
|
||||
<th id="file" class="name" aria-sort="none" data-shortcut="f">File<span class="arrows"></span></th>
|
||||
<th id="region" class="name" aria-sort="none" data-default-sort-order="ascending" data-shortcut="n">class<span class="arrows"></span></th>
|
||||
<th class="spacer"> </th>
|
||||
<th id="statements" aria-sort="none" data-default-sort-order="descending" data-shortcut="s">statements<span class="arrows"></span></th>
|
||||
<th id="missing" aria-sort="none" data-default-sort-order="descending" data-shortcut="m">missing<span class="arrows"></span></th>
|
||||
<th id="excluded" aria-sort="none" data-default-sort-order="descending" data-shortcut="x">excluded<span class="arrows"></span></th>
|
||||
<th class="spacer"> </th>
|
||||
<th id="coverage" aria-sort="none" data-shortcut="c">coverage<span class="arrows"></span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="region">
|
||||
<td class="name"><a href="z_de1a740d5dc98ffd_alerts_py.html">scripts / alerts.py</a></td>
|
||||
<td class="name"><a href="z_de1a740d5dc98ffd_alerts_py.html"><data value=''><span class='no-noun'>(no class)</span></data></a></td>
|
||||
<td class="spacer"> </td>
|
||||
<td>292</td>
|
||||
<td>118</td>
|
||||
<td>2</td>
|
||||
<td class="spacer"> </td>
|
||||
<td data-ratio="174 292">60%</td>
|
||||
</tr>
|
||||
<tr class="region">
|
||||
<td class="name"><a href="z_de1a740d5dc98ffd_briefing_py.html">scripts / briefing.py</a></td>
|
||||
<td class="name"><a href="z_de1a740d5dc98ffd_briefing_py.html"><data value=''><span class='no-noun'>(no class)</span></data></a></td>
|
||||
<td class="spacer"> </td>
|
||||
<td>87</td>
|
||||
<td>38</td>
|
||||
<td>2</td>
|
||||
<td class="spacer"> </td>
|
||||
<td data-ratio="49 87">56%</td>
|
||||
</tr>
|
||||
<tr class="region">
|
||||
<td class="name"><a href="z_de1a740d5dc98ffd_earnings_py.html#t507">scripts / earnings.py</a></td>
|
||||
<td class="name"><a href="z_de1a740d5dc98ffd_earnings_py.html#t507"><data value='Args'>get_briefing_section.Args</data></a></td>
|
||||
<td class="spacer"> </td>
|
||||
<td>0</td>
|
||||
<td>0</td>
|
||||
<td>0</td>
|
||||
<td class="spacer"> </td>
|
||||
<td data-ratio="0 0">100%</td>
|
||||
</tr>
|
||||
<tr class="region">
|
||||
<td class="name"><a href="z_de1a740d5dc98ffd_earnings_py.html">scripts / earnings.py</a></td>
|
||||
<td class="name"><a href="z_de1a740d5dc98ffd_earnings_py.html"><data value=''><span class='no-noun'>(no class)</span></data></a></td>
|
||||
<td class="spacer"> </td>
|
||||
<td>329</td>
|
||||
<td>181</td>
|
||||
<td>2</td>
|
||||
<td class="spacer"> </td>
|
||||
<td data-ratio="148 329">45%</td>
|
||||
</tr>
|
||||
<tr class="region">
|
||||
<td class="name"><a href="z_de1a740d5dc98ffd_fetch_news_py.html#t109">scripts / fetch_news.py</a></td>
|
||||
<td class="name"><a href="z_de1a740d5dc98ffd_fetch_news_py.html#t109"><data value='PortfolioError'>PortfolioError</data></a></td>
|
||||
<td class="spacer"> </td>
|
||||
<td>0</td>
|
||||
<td>0</td>
|
||||
<td>0</td>
|
||||
<td class="spacer"> </td>
|
||||
<td data-ratio="0 0">100%</td>
|
||||
</tr>
|
||||
<tr class="region">
|
||||
<td class="name"><a href="z_de1a740d5dc98ffd_fetch_news_py.html">scripts / fetch_news.py</a></td>
|
||||
<td class="name"><a href="z_de1a740d5dc98ffd_fetch_news_py.html"><data value=''><span class='no-noun'>(no class)</span></data></a></td>
|
||||
<td class="spacer"> </td>
|
||||
<td>589</td>
|
||||
<td>377</td>
|
||||
<td>2</td>
|
||||
<td class="spacer"> </td>
|
||||
<td data-ratio="212 589">36%</td>
|
||||
</tr>
|
||||
<tr class="region">
|
||||
<td class="name"><a href="z_de1a740d5dc98ffd_portfolio_py.html">scripts / portfolio.py</a></td>
|
||||
<td class="name"><a href="z_de1a740d5dc98ffd_portfolio_py.html"><data value=''><span class='no-noun'>(no class)</span></data></a></td>
|
||||
<td class="spacer"> </td>
|
||||
<td>183</td>
|
||||
<td>124</td>
|
||||
<td>2</td>
|
||||
<td class="spacer"> </td>
|
||||
<td data-ratio="59 183">32%</td>
|
||||
</tr>
|
||||
<tr class="region">
|
||||
<td class="name"><a href="z_de1a740d5dc98ffd_ranking_py.html">scripts / ranking.py</a></td>
|
||||
<td class="name"><a href="z_de1a740d5dc98ffd_ranking_py.html"><data value=''><span class='no-noun'>(no class)</span></data></a></td>
|
||||
<td class="spacer"> </td>
|
||||
<td>147</td>
|
||||
<td>21</td>
|
||||
<td>9</td>
|
||||
<td class="spacer"> </td>
|
||||
<td data-ratio="126 147">86%</td>
|
||||
</tr>
|
||||
<tr class="region">
|
||||
<td class="name"><a href="z_de1a740d5dc98ffd_research_py.html">scripts / research.py</a></td>
|
||||
<td class="name"><a href="z_de1a740d5dc98ffd_research_py.html"><data value=''><span class='no-noun'>(no class)</span></data></a></td>
|
||||
<td class="spacer"> </td>
|
||||
<td>130</td>
|
||||
<td>45</td>
|
||||
<td>2</td>
|
||||
<td class="spacer"> </td>
|
||||
<td data-ratio="85 130">65%</td>
|
||||
</tr>
|
||||
<tr class="region">
|
||||
<td class="name"><a href="z_de1a740d5dc98ffd_setup_py.html">scripts / setup.py</a></td>
|
||||
<td class="name"><a href="z_de1a740d5dc98ffd_setup_py.html"><data value=''><span class='no-noun'>(no class)</span></data></a></td>
|
||||
<td class="spacer"> </td>
|
||||
<td>168</td>
|
||||
<td>124</td>
|
||||
<td>2</td>
|
||||
<td class="spacer"> </td>
|
||||
<td data-ratio="44 168">26%</td>
|
||||
</tr>
|
||||
<tr class="region">
|
||||
<td class="name"><a href="z_de1a740d5dc98ffd_stocks_py.html">scripts / stocks.py</a></td>
|
||||
<td class="name"><a href="z_de1a740d5dc98ffd_stocks_py.html"><data value=''><span class='no-noun'>(no class)</span></data></a></td>
|
||||
<td class="spacer"> </td>
|
||||
<td>184</td>
|
||||
<td>87</td>
|
||||
<td>2</td>
|
||||
<td class="spacer"> </td>
|
||||
<td data-ratio="97 184">53%</td>
|
||||
</tr>
|
||||
<tr class="region">
|
||||
<td class="name"><a href="z_de1a740d5dc98ffd_summarize_py.html#t64">scripts / summarize.py</a></td>
|
||||
<td class="name"><a href="z_de1a740d5dc98ffd_summarize_py.html#t64"><data value='MoverContext'>MoverContext</data></a></td>
|
||||
<td class="spacer"> </td>
|
||||
<td>0</td>
|
||||
<td>0</td>
|
||||
<td>0</td>
|
||||
<td class="spacer"> </td>
|
||||
<td data-ratio="0 0">100%</td>
|
||||
</tr>
|
||||
<tr class="region">
|
||||
<td class="name"><a href="z_de1a740d5dc98ffd_summarize_py.html#t76">scripts / summarize.py</a></td>
|
||||
<td class="name"><a href="z_de1a740d5dc98ffd_summarize_py.html#t76"><data value='SectorCluster'>SectorCluster</data></a></td>
|
||||
<td class="spacer"> </td>
|
||||
<td>0</td>
|
||||
<td>0</td>
|
||||
<td>0</td>
|
||||
<td class="spacer"> </td>
|
||||
<td data-ratio="0 0">100%</td>
|
||||
</tr>
|
||||
<tr class="region">
|
||||
<td class="name"><a href="z_de1a740d5dc98ffd_summarize_py.html#t86">scripts / summarize.py</a></td>
|
||||
<td class="name"><a href="z_de1a740d5dc98ffd_summarize_py.html#t86"><data value='WatchpointsData'>WatchpointsData</data></a></td>
|
||||
<td class="spacer"> </td>
|
||||
<td>0</td>
|
||||
<td>0</td>
|
||||
<td>0</td>
|
||||
<td class="spacer"> </td>
|
||||
<td data-ratio="0 0">100%</td>
|
||||
</tr>
|
||||
<tr class="region">
|
||||
<td class="name"><a href="z_de1a740d5dc98ffd_summarize_py.html">scripts / summarize.py</a></td>
|
||||
<td class="name"><a href="z_de1a740d5dc98ffd_summarize_py.html"><data value=''><span class='no-noun'>(no class)</span></data></a></td>
|
||||
<td class="spacer"> </td>
|
||||
<td>972</td>
|
||||
<td>462</td>
|
||||
<td>2</td>
|
||||
<td class="spacer"> </td>
|
||||
<td data-ratio="510 972">52%</td>
|
||||
</tr>
|
||||
<tr class="region">
|
||||
<td class="name"><a href="z_de1a740d5dc98ffd_translate_portfolio_py.html">scripts / translate_portfolio.py</a></td>
|
||||
<td class="name"><a href="z_de1a740d5dc98ffd_translate_portfolio_py.html"><data value=''><span class='no-noun'>(no class)</span></data></a></td>
|
||||
<td class="spacer"> </td>
|
||||
<td>88</td>
|
||||
<td>88</td>
|
||||
<td>2</td>
|
||||
<td class="spacer"> </td>
|
||||
<td data-ratio="0 88">0%</td>
|
||||
</tr>
|
||||
<tr class="region">
|
||||
<td class="name"><a href="z_de1a740d5dc98ffd_utils_py.html">scripts / utils.py</a></td>
|
||||
<td class="name"><a href="z_de1a740d5dc98ffd_utils_py.html"><data value=''><span class='no-noun'>(no class)</span></data></a></td>
|
||||
<td class="spacer"> </td>
|
||||
<td>34</td>
|
||||
<td>10</td>
|
||||
<td>0</td>
|
||||
<td class="spacer"> </td>
|
||||
<td data-ratio="24 34">71%</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr class="total">
|
||||
<td class="name">Total</td>
|
||||
<td class="name"> </td>
|
||||
<td class="spacer"> </td>
|
||||
<td>3203</td>
|
||||
<td>1675</td>
|
||||
<td>29</td>
|
||||
<td class="spacer"> </td>
|
||||
<td data-ratio="1528 3203">48%</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
<p id="no_rows">
|
||||
No items found using the specified filter.
|
||||
</p>
|
||||
</main>
|
||||
<footer>
|
||||
<div class="content">
|
||||
<p>
|
||||
<a class="nav" href="https://coverage.readthedocs.io/en/7.13.2">coverage.py v7.13.2</a>,
|
||||
created at 2026-02-01 16:34 -0800
|
||||
</p>
|
||||
</div>
|
||||
<aside class="hidden">
|
||||
<a id="prevFileLink" class="nav" href=""></a>
|
||||
<a id="nextFileLink" class="nav" href=""></a>
|
||||
<button type="button" class="button_prev_file" data-shortcut="["></button>
|
||||
<button type="button" class="button_next_file" data-shortcut="]"></button>
|
||||
<button type="button" class="button_show_hide_help" data-shortcut="?"></button>
|
||||
</aside>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
735
htmlcov/coverage_html_cb_dd2e7eb5.js
generated
Normal file
735
htmlcov/coverage_html_cb_dd2e7eb5.js
generated
Normal file
@@ -0,0 +1,735 @@
|
||||
// Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
||||
// For details: https://github.com/coveragepy/coveragepy/blob/main/NOTICE.txt
|
||||
|
||||
// Coverage.py HTML report browser code.
|
||||
/*jslint browser: true, sloppy: true, vars: true, plusplus: true, maxerr: 50, indent: 4 */
|
||||
/*global coverage: true, document, window, $ */
|
||||
|
||||
coverage = {};
|
||||
|
||||
// General helpers
|
||||
function debounce(callback, wait) {
|
||||
let timeoutId = null;
|
||||
return function(...args) {
|
||||
clearTimeout(timeoutId);
|
||||
timeoutId = setTimeout(() => {
|
||||
callback.apply(this, args);
|
||||
}, wait);
|
||||
};
|
||||
};
|
||||
|
||||
function checkVisible(element) {
|
||||
const rect = element.getBoundingClientRect();
|
||||
const viewBottom = Math.max(document.documentElement.clientHeight, window.innerHeight);
|
||||
const viewTop = 30;
|
||||
return !(rect.bottom < viewTop || rect.top >= viewBottom);
|
||||
}
|
||||
|
||||
function on_click(sel, fn) {
|
||||
const elt = document.querySelector(sel);
|
||||
if (elt) {
|
||||
elt.addEventListener("click", fn);
|
||||
}
|
||||
}
|
||||
|
||||
// Helpers for table sorting
|
||||
function getCellValue(row, column = 0) {
|
||||
const cell = row.cells[column] // nosemgrep: eslint.detect-object-injection
|
||||
if (cell.childElementCount == 1) {
|
||||
var child = cell.firstElementChild;
|
||||
if (child.tagName === "A") {
|
||||
child = child.firstElementChild;
|
||||
}
|
||||
if (child instanceof HTMLDataElement && child.value) {
|
||||
return child.value;
|
||||
}
|
||||
}
|
||||
return cell.innerText || cell.textContent;
|
||||
}
|
||||
|
||||
function rowComparator(rowA, rowB, column = 0) {
|
||||
let valueA = getCellValue(rowA, column);
|
||||
let valueB = getCellValue(rowB, column);
|
||||
if (!isNaN(valueA) && !isNaN(valueB)) {
|
||||
return valueA - valueB;
|
||||
}
|
||||
return valueA.localeCompare(valueB, undefined, {numeric: true});
|
||||
}
|
||||
|
||||
function sortColumn(th) {
|
||||
// Get the current sorting direction of the selected header,
|
||||
// clear state on other headers and then set the new sorting direction.
|
||||
const currentSortOrder = th.getAttribute("aria-sort");
|
||||
[...th.parentElement.cells].forEach(header => header.setAttribute("aria-sort", "none"));
|
||||
var direction;
|
||||
if (currentSortOrder === "none") {
|
||||
direction = th.dataset.defaultSortOrder || "ascending";
|
||||
}
|
||||
else if (currentSortOrder === "ascending") {
|
||||
direction = "descending";
|
||||
}
|
||||
else {
|
||||
direction = "ascending";
|
||||
}
|
||||
th.setAttribute("aria-sort", direction);
|
||||
|
||||
const column = [...th.parentElement.cells].indexOf(th)
|
||||
|
||||
// Sort all rows and afterwards append them in order to move them in the DOM.
|
||||
Array.from(th.closest("table").querySelectorAll("tbody tr"))
|
||||
.sort((rowA, rowB) => rowComparator(rowA, rowB, column) * (direction === "ascending" ? 1 : -1))
|
||||
.forEach(tr => tr.parentElement.appendChild(tr));
|
||||
|
||||
// Save the sort order for next time.
|
||||
if (th.id !== "region") {
|
||||
let th_id = "file"; // Sort by file if we don't have a column id
|
||||
let current_direction = direction;
|
||||
const stored_list = localStorage.getItem(coverage.INDEX_SORT_STORAGE);
|
||||
if (stored_list) {
|
||||
({th_id, direction} = JSON.parse(stored_list))
|
||||
}
|
||||
localStorage.setItem(coverage.INDEX_SORT_STORAGE, JSON.stringify({
|
||||
"th_id": th.id,
|
||||
"direction": current_direction
|
||||
}));
|
||||
if (th.id !== th_id || document.getElementById("region")) {
|
||||
// Sort column has changed, unset sorting by function or class.
|
||||
localStorage.setItem(coverage.SORTED_BY_REGION, JSON.stringify({
|
||||
"by_region": false,
|
||||
"region_direction": current_direction
|
||||
}));
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Sort column has changed to by function or class, remember that.
|
||||
localStorage.setItem(coverage.SORTED_BY_REGION, JSON.stringify({
|
||||
"by_region": true,
|
||||
"region_direction": direction
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
// Find all the elements with data-shortcut attribute, and use them to assign a shortcut key.
|
||||
coverage.assign_shortkeys = function () {
|
||||
document.querySelectorAll("[data-shortcut]").forEach(element => {
|
||||
document.addEventListener("keypress", event => {
|
||||
if (event.target.tagName.toLowerCase() === "input") {
|
||||
return; // ignore keypress from search filter
|
||||
}
|
||||
if (event.key === element.dataset.shortcut) {
|
||||
element.click();
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Create the events for the filter box.
|
||||
coverage.wire_up_filter = function () {
|
||||
// Populate the filter and hide100 inputs if there are saved values for them.
|
||||
const saved_filter_value = localStorage.getItem(coverage.FILTER_STORAGE);
|
||||
if (saved_filter_value) {
|
||||
document.getElementById("filter").value = saved_filter_value;
|
||||
}
|
||||
const saved_hide100_value = localStorage.getItem(coverage.HIDE100_STORAGE);
|
||||
if (saved_hide100_value) {
|
||||
document.getElementById("hide100").checked = JSON.parse(saved_hide100_value);
|
||||
}
|
||||
|
||||
// Cache elements.
|
||||
const table = document.querySelector("table.index");
|
||||
const table_body_rows = table.querySelectorAll("tbody tr");
|
||||
const no_rows = document.getElementById("no_rows");
|
||||
|
||||
const footer = table.tFoot.rows[0];
|
||||
const ratio_columns = Array.from(footer.cells).map(cell => Boolean(cell.dataset.ratio));
|
||||
|
||||
// Observe filter keyevents.
|
||||
const filter_handler = (event => {
|
||||
// Keep running total of each metric, first index contains number of shown rows
|
||||
const totals = ratio_columns.map(
|
||||
is_ratio => is_ratio ? {"numer": 0, "denom": 0} : 0
|
||||
);
|
||||
|
||||
var text = document.getElementById("filter").value;
|
||||
// Store filter value
|
||||
localStorage.setItem(coverage.FILTER_STORAGE, text);
|
||||
const casefold = (text === text.toLowerCase());
|
||||
const hide100 = document.getElementById("hide100").checked;
|
||||
// Store hide value.
|
||||
localStorage.setItem(coverage.HIDE100_STORAGE, JSON.stringify(hide100));
|
||||
|
||||
// Hide / show elements.
|
||||
table_body_rows.forEach(row => {
|
||||
var show = false;
|
||||
// Check the text filter.
|
||||
for (let column = 0; column < totals.length; column++) {
|
||||
cell = row.cells[column];
|
||||
if (cell.classList.contains("name")) {
|
||||
var celltext = cell.textContent;
|
||||
if (casefold) {
|
||||
celltext = celltext.toLowerCase();
|
||||
}
|
||||
if (celltext.includes(text)) {
|
||||
show = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check the "hide covered" filter.
|
||||
if (show && hide100) {
|
||||
const [numer, denom] = row.cells[row.cells.length - 1].dataset.ratio.split(" ");
|
||||
show = (numer !== denom);
|
||||
}
|
||||
|
||||
if (!show) {
|
||||
// hide
|
||||
row.classList.add("hidden");
|
||||
return;
|
||||
}
|
||||
|
||||
// show
|
||||
row.classList.remove("hidden");
|
||||
totals[0]++;
|
||||
|
||||
for (let column = 0; column < totals.length; column++) {
|
||||
// Accumulate dynamic totals
|
||||
cell = row.cells[column] // nosemgrep: eslint.detect-object-injection
|
||||
if (cell.matches(".name, .spacer")) {
|
||||
continue;
|
||||
}
|
||||
if (ratio_columns[column] && cell.dataset.ratio) {
|
||||
// Column stores a ratio
|
||||
const [numer, denom] = cell.dataset.ratio.split(" ");
|
||||
totals[column]["numer"] += parseInt(numer, 10); // nosemgrep: eslint.detect-object-injection
|
||||
totals[column]["denom"] += parseInt(denom, 10); // nosemgrep: eslint.detect-object-injection
|
||||
}
|
||||
else {
|
||||
totals[column] += parseInt(cell.textContent, 10); // nosemgrep: eslint.detect-object-injection
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Show placeholder if no rows will be displayed.
|
||||
if (!totals[0]) {
|
||||
// Show placeholder, hide table.
|
||||
no_rows.style.display = "block";
|
||||
table.style.display = "none";
|
||||
return;
|
||||
}
|
||||
|
||||
// Hide placeholder, show table.
|
||||
no_rows.style.display = null;
|
||||
table.style.display = null;
|
||||
|
||||
// Calculate new dynamic sum values based on visible rows.
|
||||
for (let column = 0; column < totals.length; column++) {
|
||||
// Get footer cell element.
|
||||
const cell = footer.cells[column]; // nosemgrep: eslint.detect-object-injection
|
||||
if (cell.matches(".name, .spacer")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Set value into dynamic footer cell element.
|
||||
if (ratio_columns[column]) {
|
||||
// Percentage column uses the numerator and denominator,
|
||||
// and adapts to the number of decimal places.
|
||||
const match = /\.([0-9]+)/.exec(cell.textContent);
|
||||
const places = match ? match[1].length : 0;
|
||||
const { numer, denom } = totals[column]; // nosemgrep: eslint.detect-object-injection
|
||||
cell.dataset.ratio = `${numer} ${denom}`;
|
||||
// Check denom to prevent NaN if filtered files contain no statements
|
||||
cell.textContent = denom
|
||||
? `${(numer * 100 / denom).toFixed(places)}%`
|
||||
: `${(100).toFixed(places)}%`;
|
||||
}
|
||||
else {
|
||||
cell.textContent = totals[column]; // nosemgrep: eslint.detect-object-injection
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("filter").addEventListener("input", debounce(filter_handler));
|
||||
document.getElementById("hide100").addEventListener("input", debounce(filter_handler));
|
||||
|
||||
// Trigger change event on setup, to force filter on page refresh
|
||||
// (filter value may still be present).
|
||||
document.getElementById("filter").dispatchEvent(new Event("input"));
|
||||
document.getElementById("hide100").dispatchEvent(new Event("input"));
|
||||
};
|
||||
coverage.FILTER_STORAGE = "COVERAGE_FILTER_VALUE";
|
||||
coverage.HIDE100_STORAGE = "COVERAGE_HIDE100_VALUE";
|
||||
|
||||
// Set up the click-to-sort columns.
|
||||
coverage.wire_up_sorting = function () {
|
||||
document.querySelectorAll("[data-sortable] th[aria-sort]").forEach(
|
||||
th => th.addEventListener("click", e => sortColumn(e.target))
|
||||
);
|
||||
|
||||
// Look for a localStorage item containing previous sort settings:
|
||||
let th_id = "file", direction = "ascending";
|
||||
const stored_list = localStorage.getItem(coverage.INDEX_SORT_STORAGE);
|
||||
if (stored_list) {
|
||||
({th_id, direction} = JSON.parse(stored_list));
|
||||
}
|
||||
let by_region = false, region_direction = "ascending";
|
||||
const sorted_by_region = localStorage.getItem(coverage.SORTED_BY_REGION);
|
||||
if (sorted_by_region) {
|
||||
({
|
||||
by_region,
|
||||
region_direction
|
||||
} = JSON.parse(sorted_by_region));
|
||||
}
|
||||
|
||||
const region_id = "region";
|
||||
if (by_region && document.getElementById(region_id)) {
|
||||
direction = region_direction;
|
||||
}
|
||||
// If we are in a page that has a column with id of "region", sort on
|
||||
// it if the last sort was by function or class.
|
||||
let th;
|
||||
if (document.getElementById(region_id)) {
|
||||
th = document.getElementById(by_region ? region_id : th_id);
|
||||
}
|
||||
else {
|
||||
th = document.getElementById(th_id);
|
||||
}
|
||||
th.setAttribute("aria-sort", direction === "ascending" ? "descending" : "ascending");
|
||||
th.click()
|
||||
};
|
||||
|
||||
coverage.INDEX_SORT_STORAGE = "COVERAGE_INDEX_SORT_2";
|
||||
coverage.SORTED_BY_REGION = "COVERAGE_SORT_REGION";
|
||||
|
||||
// Loaded on index.html
|
||||
coverage.index_ready = function () {
|
||||
coverage.assign_shortkeys();
|
||||
coverage.wire_up_filter();
|
||||
coverage.wire_up_sorting();
|
||||
|
||||
on_click(".button_prev_file", coverage.to_prev_file);
|
||||
on_click(".button_next_file", coverage.to_next_file);
|
||||
|
||||
on_click(".button_show_hide_help", coverage.show_hide_help);
|
||||
};
|
||||
|
||||
// -- pyfile stuff --
|
||||
|
||||
coverage.LINE_FILTERS_STORAGE = "COVERAGE_LINE_FILTERS";
|
||||
|
||||
coverage.pyfile_ready = function () {
|
||||
// If we're directed to a particular line number, highlight the line.
|
||||
var frag = location.hash;
|
||||
if (frag.length > 2 && frag[1] === "t") {
|
||||
document.querySelector(frag).closest(".n").classList.add("highlight");
|
||||
coverage.set_sel(parseInt(frag.substr(2), 10));
|
||||
}
|
||||
else {
|
||||
coverage.set_sel(0);
|
||||
}
|
||||
|
||||
on_click(".button_toggle_run", coverage.toggle_lines);
|
||||
on_click(".button_toggle_mis", coverage.toggle_lines);
|
||||
on_click(".button_toggle_exc", coverage.toggle_lines);
|
||||
on_click(".button_toggle_par", coverage.toggle_lines);
|
||||
|
||||
on_click(".button_next_chunk", coverage.to_next_chunk_nicely);
|
||||
on_click(".button_prev_chunk", coverage.to_prev_chunk_nicely);
|
||||
on_click(".button_top_of_page", coverage.to_top);
|
||||
on_click(".button_first_chunk", coverage.to_first_chunk);
|
||||
|
||||
on_click(".button_prev_file", coverage.to_prev_file);
|
||||
on_click(".button_next_file", coverage.to_next_file);
|
||||
on_click(".button_to_index", coverage.to_index);
|
||||
|
||||
on_click(".button_show_hide_help", coverage.show_hide_help);
|
||||
|
||||
coverage.filters = undefined;
|
||||
try {
|
||||
coverage.filters = localStorage.getItem(coverage.LINE_FILTERS_STORAGE);
|
||||
} catch(err) {}
|
||||
|
||||
if (coverage.filters) {
|
||||
coverage.filters = JSON.parse(coverage.filters);
|
||||
}
|
||||
else {
|
||||
coverage.filters = {run: false, exc: true, mis: true, par: true};
|
||||
}
|
||||
|
||||
for (cls in coverage.filters) {
|
||||
coverage.set_line_visibilty(cls, coverage.filters[cls]); // nosemgrep: eslint.detect-object-injection
|
||||
}
|
||||
|
||||
coverage.assign_shortkeys();
|
||||
coverage.init_scroll_markers();
|
||||
coverage.wire_up_sticky_header();
|
||||
|
||||
document.querySelectorAll("[id^=ctxs]").forEach(
|
||||
cbox => cbox.addEventListener("click", coverage.expand_contexts)
|
||||
);
|
||||
|
||||
// Rebuild scroll markers when the window height changes.
|
||||
window.addEventListener("resize", coverage.build_scroll_markers);
|
||||
};
|
||||
|
||||
coverage.toggle_lines = function (event) {
|
||||
const btn = event.target.closest("button");
|
||||
const category = btn.value
|
||||
const show = !btn.classList.contains("show_" + category);
|
||||
coverage.set_line_visibilty(category, show);
|
||||
coverage.build_scroll_markers();
|
||||
coverage.filters[category] = show;
|
||||
try {
|
||||
localStorage.setItem(coverage.LINE_FILTERS_STORAGE, JSON.stringify(coverage.filters));
|
||||
} catch(err) {}
|
||||
};
|
||||
|
||||
coverage.set_line_visibilty = function (category, should_show) {
|
||||
const cls = "show_" + category;
|
||||
const btn = document.querySelector(".button_toggle_" + category);
|
||||
if (btn) {
|
||||
if (should_show) {
|
||||
document.querySelectorAll("#source ." + category).forEach(e => e.classList.add(cls));
|
||||
btn.classList.add(cls);
|
||||
}
|
||||
else {
|
||||
document.querySelectorAll("#source ." + category).forEach(e => e.classList.remove(cls));
|
||||
btn.classList.remove(cls);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Return the nth line div.
|
||||
coverage.line_elt = function (n) {
|
||||
return document.getElementById("t" + n)?.closest("p");
|
||||
};
|
||||
|
||||
// Set the selection. b and e are line numbers.
|
||||
coverage.set_sel = function (b, e) {
|
||||
// The first line selected.
|
||||
coverage.sel_begin = b;
|
||||
// The next line not selected.
|
||||
coverage.sel_end = (e === undefined) ? b+1 : e;
|
||||
};
|
||||
|
||||
coverage.to_top = function () {
|
||||
coverage.set_sel(0, 1);
|
||||
coverage.scroll_window(0);
|
||||
};
|
||||
|
||||
coverage.to_first_chunk = function () {
|
||||
coverage.set_sel(0, 1);
|
||||
coverage.to_next_chunk();
|
||||
};
|
||||
|
||||
coverage.to_prev_file = function () {
|
||||
window.location = document.getElementById("prevFileLink").href;
|
||||
}
|
||||
|
||||
coverage.to_next_file = function () {
|
||||
window.location = document.getElementById("nextFileLink").href;
|
||||
}
|
||||
|
||||
coverage.to_index = function () {
|
||||
location.href = document.getElementById("indexLink").href;
|
||||
}
|
||||
|
||||
coverage.show_hide_help = function () {
|
||||
const helpCheck = document.getElementById("help_panel_state")
|
||||
helpCheck.checked = !helpCheck.checked;
|
||||
}
|
||||
|
||||
// Return a string indicating what kind of chunk this line belongs to,
|
||||
// or null if not a chunk.
|
||||
coverage.chunk_indicator = function (line_elt) {
|
||||
const classes = line_elt?.className;
|
||||
if (!classes) {
|
||||
return null;
|
||||
}
|
||||
const match = classes.match(/\bshow_\w+\b/);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
return match[0];
|
||||
};
|
||||
|
||||
coverage.to_next_chunk = function () {
|
||||
const c = coverage;
|
||||
|
||||
// Find the start of the next colored chunk.
|
||||
var probe = c.sel_end;
|
||||
var chunk_indicator, probe_line;
|
||||
while (true) {
|
||||
probe_line = c.line_elt(probe);
|
||||
if (!probe_line) {
|
||||
return;
|
||||
}
|
||||
chunk_indicator = c.chunk_indicator(probe_line);
|
||||
if (chunk_indicator) {
|
||||
break;
|
||||
}
|
||||
probe++;
|
||||
}
|
||||
|
||||
// There's a next chunk, `probe` points to it.
|
||||
var begin = probe;
|
||||
|
||||
// Find the end of this chunk.
|
||||
var next_indicator = chunk_indicator;
|
||||
while (next_indicator === chunk_indicator) {
|
||||
probe++;
|
||||
probe_line = c.line_elt(probe);
|
||||
next_indicator = c.chunk_indicator(probe_line);
|
||||
}
|
||||
c.set_sel(begin, probe);
|
||||
c.show_selection();
|
||||
};
|
||||
|
||||
coverage.to_prev_chunk = function () {
|
||||
const c = coverage;
|
||||
|
||||
// Find the end of the prev colored chunk.
|
||||
var probe = c.sel_begin-1;
|
||||
var probe_line = c.line_elt(probe);
|
||||
if (!probe_line) {
|
||||
return;
|
||||
}
|
||||
var chunk_indicator = c.chunk_indicator(probe_line);
|
||||
while (probe > 1 && !chunk_indicator) {
|
||||
probe--;
|
||||
probe_line = c.line_elt(probe);
|
||||
if (!probe_line) {
|
||||
return;
|
||||
}
|
||||
chunk_indicator = c.chunk_indicator(probe_line);
|
||||
}
|
||||
|
||||
// There's a prev chunk, `probe` points to its last line.
|
||||
var end = probe+1;
|
||||
|
||||
// Find the beginning of this chunk.
|
||||
var prev_indicator = chunk_indicator;
|
||||
while (prev_indicator === chunk_indicator) {
|
||||
probe--;
|
||||
if (probe <= 0) {
|
||||
return;
|
||||
}
|
||||
probe_line = c.line_elt(probe);
|
||||
prev_indicator = c.chunk_indicator(probe_line);
|
||||
}
|
||||
c.set_sel(probe+1, end);
|
||||
c.show_selection();
|
||||
};
|
||||
|
||||
// Returns 0, 1, or 2: how many of the two ends of the selection are on
|
||||
// the screen right now?
|
||||
coverage.selection_ends_on_screen = function () {
|
||||
if (coverage.sel_begin === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const begin = coverage.line_elt(coverage.sel_begin);
|
||||
const end = coverage.line_elt(coverage.sel_end-1);
|
||||
|
||||
return (
|
||||
(checkVisible(begin) ? 1 : 0)
|
||||
+ (checkVisible(end) ? 1 : 0)
|
||||
);
|
||||
};
|
||||
|
||||
coverage.to_next_chunk_nicely = function () {
|
||||
if (coverage.selection_ends_on_screen() === 0) {
|
||||
// The selection is entirely off the screen:
|
||||
// Set the top line on the screen as selection.
|
||||
|
||||
// This will select the top-left of the viewport
|
||||
// As this is most likely the span with the line number we take the parent
|
||||
const line = document.elementFromPoint(0, 0).parentElement;
|
||||
if (line.parentElement !== document.getElementById("source")) {
|
||||
// The element is not a source line but the header or similar
|
||||
coverage.select_line_or_chunk(1);
|
||||
}
|
||||
else {
|
||||
// We extract the line number from the id
|
||||
coverage.select_line_or_chunk(parseInt(line.id.substring(1), 10));
|
||||
}
|
||||
}
|
||||
coverage.to_next_chunk();
|
||||
};
|
||||
|
||||
coverage.to_prev_chunk_nicely = function () {
|
||||
if (coverage.selection_ends_on_screen() === 0) {
|
||||
// The selection is entirely off the screen:
|
||||
// Set the lowest line on the screen as selection.
|
||||
|
||||
// This will select the bottom-left of the viewport
|
||||
// As this is most likely the span with the line number we take the parent
|
||||
const line = document.elementFromPoint(document.documentElement.clientHeight-1, 0).parentElement;
|
||||
if (line.parentElement !== document.getElementById("source")) {
|
||||
// The element is not a source line but the header or similar
|
||||
coverage.select_line_or_chunk(coverage.lines_len);
|
||||
}
|
||||
else {
|
||||
// We extract the line number from the id
|
||||
coverage.select_line_or_chunk(parseInt(line.id.substring(1), 10));
|
||||
}
|
||||
}
|
||||
coverage.to_prev_chunk();
|
||||
};
|
||||
|
||||
// Select line number lineno, or if it is in a colored chunk, select the
|
||||
// entire chunk
|
||||
coverage.select_line_or_chunk = function (lineno) {
|
||||
var c = coverage;
|
||||
var probe_line = c.line_elt(lineno);
|
||||
if (!probe_line) {
|
||||
return;
|
||||
}
|
||||
var the_indicator = c.chunk_indicator(probe_line);
|
||||
if (the_indicator) {
|
||||
// The line is in a highlighted chunk.
|
||||
// Search backward for the first line.
|
||||
var probe = lineno;
|
||||
var indicator = the_indicator;
|
||||
while (probe > 0 && indicator === the_indicator) {
|
||||
probe--;
|
||||
probe_line = c.line_elt(probe);
|
||||
if (!probe_line) {
|
||||
break;
|
||||
}
|
||||
indicator = c.chunk_indicator(probe_line);
|
||||
}
|
||||
var begin = probe + 1;
|
||||
|
||||
// Search forward for the last line.
|
||||
probe = lineno;
|
||||
indicator = the_indicator;
|
||||
while (indicator === the_indicator) {
|
||||
probe++;
|
||||
probe_line = c.line_elt(probe);
|
||||
indicator = c.chunk_indicator(probe_line);
|
||||
}
|
||||
|
||||
coverage.set_sel(begin, probe);
|
||||
}
|
||||
else {
|
||||
coverage.set_sel(lineno);
|
||||
}
|
||||
};
|
||||
|
||||
coverage.show_selection = function () {
|
||||
// Highlight the lines in the chunk
|
||||
document.querySelectorAll("#source .highlight").forEach(e => e.classList.remove("highlight"));
|
||||
for (let probe = coverage.sel_begin; probe < coverage.sel_end; probe++) {
|
||||
coverage.line_elt(probe).querySelector(".n").classList.add("highlight");
|
||||
}
|
||||
|
||||
coverage.scroll_to_selection();
|
||||
};
|
||||
|
||||
coverage.scroll_to_selection = function () {
|
||||
// Scroll the page if the chunk isn't fully visible.
|
||||
if (coverage.selection_ends_on_screen() < 2) {
|
||||
const element = coverage.line_elt(coverage.sel_begin);
|
||||
coverage.scroll_window(element.offsetTop - 60);
|
||||
}
|
||||
};
|
||||
|
||||
coverage.scroll_window = function (to_pos) {
|
||||
window.scroll({top: to_pos, behavior: "smooth"});
|
||||
};
|
||||
|
||||
coverage.init_scroll_markers = function () {
|
||||
// Init some variables
|
||||
coverage.lines_len = document.querySelectorAll("#source > p").length;
|
||||
|
||||
// Build html
|
||||
coverage.build_scroll_markers();
|
||||
};
|
||||
|
||||
coverage.build_scroll_markers = function () {
|
||||
const temp_scroll_marker = document.getElementById("scroll_marker")
|
||||
if (temp_scroll_marker) temp_scroll_marker.remove();
|
||||
// Don't build markers if the window has no scroll bar.
|
||||
if (document.body.scrollHeight <= window.innerHeight) {
|
||||
return;
|
||||
}
|
||||
|
||||
const marker_scale = window.innerHeight / document.body.scrollHeight;
|
||||
const line_height = Math.min(Math.max(3, window.innerHeight / coverage.lines_len), 10);
|
||||
|
||||
let previous_line = -99, last_mark, last_top;
|
||||
|
||||
const scroll_marker = document.createElement("div");
|
||||
scroll_marker.id = "scroll_marker";
|
||||
document.getElementById("source").querySelectorAll(
|
||||
"p.show_run, p.show_mis, p.show_exc, p.show_exc, p.show_par"
|
||||
).forEach(element => {
|
||||
const line_top = Math.floor(element.offsetTop * marker_scale);
|
||||
const line_number = parseInt(element.querySelector(".n a").id.substr(1));
|
||||
|
||||
if (line_number === previous_line + 1) {
|
||||
// If this solid missed block just make previous mark higher.
|
||||
last_mark.style.height = `${line_top + line_height - last_top}px`;
|
||||
}
|
||||
else {
|
||||
// Add colored line in scroll_marker block.
|
||||
last_mark = document.createElement("div");
|
||||
last_mark.id = `m${line_number}`;
|
||||
last_mark.classList.add("marker");
|
||||
last_mark.style.height = `${line_height}px`;
|
||||
last_mark.style.top = `${line_top}px`;
|
||||
scroll_marker.append(last_mark);
|
||||
last_top = line_top;
|
||||
}
|
||||
|
||||
previous_line = line_number;
|
||||
});
|
||||
|
||||
// Append last to prevent layout calculation
|
||||
document.body.append(scroll_marker);
|
||||
};
|
||||
|
||||
coverage.wire_up_sticky_header = function () {
|
||||
const header = document.querySelector("header");
|
||||
const header_bottom = (
|
||||
header.querySelector(".content h2").getBoundingClientRect().top -
|
||||
header.getBoundingClientRect().top
|
||||
);
|
||||
|
||||
function updateHeader() {
|
||||
if (window.scrollY > header_bottom) {
|
||||
header.classList.add("sticky");
|
||||
}
|
||||
else {
|
||||
header.classList.remove("sticky");
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("scroll", updateHeader);
|
||||
updateHeader();
|
||||
};
|
||||
|
||||
coverage.expand_contexts = function (e) {
|
||||
var ctxs = e.target.parentNode.querySelector(".ctxs");
|
||||
|
||||
if (!ctxs.classList.contains("expanded")) {
|
||||
var ctxs_text = ctxs.textContent;
|
||||
var width = Number(ctxs_text[0]);
|
||||
ctxs.textContent = "";
|
||||
for (var i = 1; i < ctxs_text.length; i += width) {
|
||||
key = ctxs_text.substring(i, i + width).trim();
|
||||
ctxs.appendChild(document.createTextNode(contexts[key]));
|
||||
ctxs.appendChild(document.createElement("br"));
|
||||
}
|
||||
ctxs.classList.add("expanded");
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
if (document.body.classList.contains("indexfile")) {
|
||||
coverage.index_ready();
|
||||
}
|
||||
else {
|
||||
coverage.pyfile_ready();
|
||||
}
|
||||
});
|
||||
1851
htmlcov/function_index.html
generated
Normal file
1851
htmlcov/function_index.html
generated
Normal file
File diff suppressed because it is too large
Load Diff
216
htmlcov/index.html
generated
Normal file
216
htmlcov/index.html
generated
Normal file
@@ -0,0 +1,216 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>Coverage report</title>
|
||||
<link rel="icon" sizes="32x32" href="favicon_32_cb_c827f16f.png">
|
||||
<link rel="stylesheet" href="style_cb_9ff733b0.css" type="text/css">
|
||||
<script src="coverage_html_cb_dd2e7eb5.js" defer></script>
|
||||
</head>
|
||||
<body class="indexfile">
|
||||
<header>
|
||||
<div class="content">
|
||||
<h1>Coverage report:
|
||||
<span class="pc_cov">48%</span>
|
||||
</h1>
|
||||
<aside id="help_panel_wrapper">
|
||||
<input id="help_panel_state" type="checkbox">
|
||||
<label for="help_panel_state">
|
||||
<img id="keyboard_icon" src="keybd_closed_cb_900cfef5.png" alt="Show/hide keyboard shortcuts">
|
||||
</label>
|
||||
<div id="help_panel">
|
||||
<p class="legend">Shortcuts on this page</p>
|
||||
<div class="keyhelp">
|
||||
<p>
|
||||
<kbd>f</kbd>
|
||||
<kbd>s</kbd>
|
||||
<kbd>m</kbd>
|
||||
<kbd>x</kbd>
|
||||
<kbd>c</kbd>
|
||||
change column sorting
|
||||
</p>
|
||||
<p>
|
||||
<kbd>[</kbd>
|
||||
<kbd>]</kbd>
|
||||
prev/next file
|
||||
</p>
|
||||
<p>
|
||||
<kbd>?</kbd> show/hide this help
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
<form id="filter_container">
|
||||
<input id="filter" type="text" value="" placeholder="filter...">
|
||||
<div>
|
||||
<input id="hide100" type="checkbox" >
|
||||
<label for="hide100">hide covered</label>
|
||||
</div>
|
||||
</form>
|
||||
<h2>
|
||||
<a class="button current">Files</a>
|
||||
<a class="button" href="function_index.html">Functions</a>
|
||||
<a class="button" href="class_index.html">Classes</a>
|
||||
</h2>
|
||||
<p class="text">
|
||||
<a class="nav" href="https://coverage.readthedocs.io/en/7.13.2">coverage.py v7.13.2</a>,
|
||||
created at 2026-02-01 16:34 -0800
|
||||
</p>
|
||||
</div>
|
||||
</header>
|
||||
<main id="index">
|
||||
<table class="index" data-sortable>
|
||||
<thead>
|
||||
<tr class="tablehead" title="Click to sort">
|
||||
<th id="file" class="name" aria-sort="none" data-shortcut="f">File<span class="arrows"></span></th>
|
||||
<th class="spacer"> </th>
|
||||
<th id="statements" aria-sort="none" data-default-sort-order="descending" data-shortcut="s">statements<span class="arrows"></span></th>
|
||||
<th id="missing" aria-sort="none" data-default-sort-order="descending" data-shortcut="m">missing<span class="arrows"></span></th>
|
||||
<th id="excluded" aria-sort="none" data-default-sort-order="descending" data-shortcut="x">excluded<span class="arrows"></span></th>
|
||||
<th class="spacer"> </th>
|
||||
<th id="coverage" aria-sort="none" data-shortcut="c">coverage<span class="arrows"></span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="region">
|
||||
<td class="name"><a href="z_de1a740d5dc98ffd_alerts_py.html">scripts / alerts.py</a></td>
|
||||
<td class="spacer"> </td>
|
||||
<td>292</td>
|
||||
<td>118</td>
|
||||
<td>2</td>
|
||||
<td class="spacer"> </td>
|
||||
<td data-ratio="174 292">60%</td>
|
||||
</tr>
|
||||
<tr class="region">
|
||||
<td class="name"><a href="z_de1a740d5dc98ffd_briefing_py.html">scripts / briefing.py</a></td>
|
||||
<td class="spacer"> </td>
|
||||
<td>87</td>
|
||||
<td>38</td>
|
||||
<td>2</td>
|
||||
<td class="spacer"> </td>
|
||||
<td data-ratio="49 87">56%</td>
|
||||
</tr>
|
||||
<tr class="region">
|
||||
<td class="name"><a href="z_de1a740d5dc98ffd_earnings_py.html">scripts / earnings.py</a></td>
|
||||
<td class="spacer"> </td>
|
||||
<td>329</td>
|
||||
<td>181</td>
|
||||
<td>2</td>
|
||||
<td class="spacer"> </td>
|
||||
<td data-ratio="148 329">45%</td>
|
||||
</tr>
|
||||
<tr class="region">
|
||||
<td class="name"><a href="z_de1a740d5dc98ffd_fetch_news_py.html">scripts / fetch_news.py</a></td>
|
||||
<td class="spacer"> </td>
|
||||
<td>589</td>
|
||||
<td>377</td>
|
||||
<td>2</td>
|
||||
<td class="spacer"> </td>
|
||||
<td data-ratio="212 589">36%</td>
|
||||
</tr>
|
||||
<tr class="region">
|
||||
<td class="name"><a href="z_de1a740d5dc98ffd_portfolio_py.html">scripts / portfolio.py</a></td>
|
||||
<td class="spacer"> </td>
|
||||
<td>183</td>
|
||||
<td>124</td>
|
||||
<td>2</td>
|
||||
<td class="spacer"> </td>
|
||||
<td data-ratio="59 183">32%</td>
|
||||
</tr>
|
||||
<tr class="region">
|
||||
<td class="name"><a href="z_de1a740d5dc98ffd_ranking_py.html">scripts / ranking.py</a></td>
|
||||
<td class="spacer"> </td>
|
||||
<td>147</td>
|
||||
<td>21</td>
|
||||
<td>9</td>
|
||||
<td class="spacer"> </td>
|
||||
<td data-ratio="126 147">86%</td>
|
||||
</tr>
|
||||
<tr class="region">
|
||||
<td class="name"><a href="z_de1a740d5dc98ffd_research_py.html">scripts / research.py</a></td>
|
||||
<td class="spacer"> </td>
|
||||
<td>130</td>
|
||||
<td>45</td>
|
||||
<td>2</td>
|
||||
<td class="spacer"> </td>
|
||||
<td data-ratio="85 130">65%</td>
|
||||
</tr>
|
||||
<tr class="region">
|
||||
<td class="name"><a href="z_de1a740d5dc98ffd_setup_py.html">scripts / setup.py</a></td>
|
||||
<td class="spacer"> </td>
|
||||
<td>168</td>
|
||||
<td>124</td>
|
||||
<td>2</td>
|
||||
<td class="spacer"> </td>
|
||||
<td data-ratio="44 168">26%</td>
|
||||
</tr>
|
||||
<tr class="region">
|
||||
<td class="name"><a href="z_de1a740d5dc98ffd_stocks_py.html">scripts / stocks.py</a></td>
|
||||
<td class="spacer"> </td>
|
||||
<td>184</td>
|
||||
<td>87</td>
|
||||
<td>2</td>
|
||||
<td class="spacer"> </td>
|
||||
<td data-ratio="97 184">53%</td>
|
||||
</tr>
|
||||
<tr class="region">
|
||||
<td class="name"><a href="z_de1a740d5dc98ffd_summarize_py.html">scripts / summarize.py</a></td>
|
||||
<td class="spacer"> </td>
|
||||
<td>972</td>
|
||||
<td>462</td>
|
||||
<td>2</td>
|
||||
<td class="spacer"> </td>
|
||||
<td data-ratio="510 972">52%</td>
|
||||
</tr>
|
||||
<tr class="region">
|
||||
<td class="name"><a href="z_de1a740d5dc98ffd_translate_portfolio_py.html">scripts / translate_portfolio.py</a></td>
|
||||
<td class="spacer"> </td>
|
||||
<td>88</td>
|
||||
<td>88</td>
|
||||
<td>2</td>
|
||||
<td class="spacer"> </td>
|
||||
<td data-ratio="0 88">0%</td>
|
||||
</tr>
|
||||
<tr class="region">
|
||||
<td class="name"><a href="z_de1a740d5dc98ffd_utils_py.html">scripts / utils.py</a></td>
|
||||
<td class="spacer"> </td>
|
||||
<td>34</td>
|
||||
<td>10</td>
|
||||
<td>0</td>
|
||||
<td class="spacer"> </td>
|
||||
<td data-ratio="24 34">71%</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr class="total">
|
||||
<td class="name">Total</td>
|
||||
<td class="spacer"> </td>
|
||||
<td>3203</td>
|
||||
<td>1675</td>
|
||||
<td>29</td>
|
||||
<td class="spacer"> </td>
|
||||
<td data-ratio="1528 3203">48%</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
<p id="no_rows">
|
||||
No items found using the specified filter.
|
||||
</p>
|
||||
</main>
|
||||
<footer>
|
||||
<div class="content">
|
||||
<p>
|
||||
<a class="nav" href="https://coverage.readthedocs.io/en/7.13.2">coverage.py v7.13.2</a>,
|
||||
created at 2026-02-01 16:34 -0800
|
||||
</p>
|
||||
</div>
|
||||
<aside class="hidden">
|
||||
<a id="prevFileLink" class="nav" href="z_de1a740d5dc98ffd_utils_py.html"></a>
|
||||
<a id="nextFileLink" class="nav" href="z_de1a740d5dc98ffd_alerts_py.html"></a>
|
||||
<button type="button" class="button_prev_file" data-shortcut="["></button>
|
||||
<button type="button" class="button_next_file" data-shortcut="]"></button>
|
||||
<button type="button" class="button_show_hide_help" data-shortcut="?"></button>
|
||||
</aside>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
1
htmlcov/status.json
generated
Normal file
1
htmlcov/status.json
generated
Normal file
@@ -0,0 +1 @@
|
||||
{"note":"This file is an internal implementation detail to speed up HTML report generation. Its format can change at any time. You might be looking for the JSON report: https://coverage.rtfd.io/cmd.html#cmd-json","format":5,"version":"7.13.2","globals":"4be31ca40797e8400fa13be69cbf6b96","files":{"z_de1a740d5dc98ffd_alerts_py":{"hash":"9256045bbdf042ec8ac79100b07f6e16","index":{"url":"z_de1a740d5dc98ffd_alerts_py.html","file":"scripts/alerts.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":292,"n_excluded":2,"n_missing":118,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_de1a740d5dc98ffd_briefing_py":{"hash":"8762987b6cbda4b3959d184f0fd43f44","index":{"url":"z_de1a740d5dc98ffd_briefing_py.html","file":"scripts/briefing.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":87,"n_excluded":2,"n_missing":38,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_de1a740d5dc98ffd_earnings_py":{"hash":"313062c04b56cd9a2f238c0c041e795c","index":{"url":"z_de1a740d5dc98ffd_earnings_py.html","file":"scripts/earnings.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":329,"n_excluded":2,"n_missing":181,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_de1a740d5dc98ffd_fetch_news_py":{"hash":"6cc00fcf9c47d99abd6109edce33ab1c","index":{"url":"z_de1a740d5dc98ffd_fetch_news_py.html","file":"scripts/fetch_news.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":589,"n_excluded":2,"n_missing":377,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_de1a740d5dc98ffd_portfolio_py":{"hash":"291475985d04ed7b91150b8eb45bb333","index":{"url":"z_de1a740d5dc98ffd_portfolio_py.html","file":"scripts/portfolio.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":183,"n_excluded":2,"n_missing":124,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_de1a740d5dc98ffd_ranking_py":{"hash":"1118174517ba630eb85b35f61798c37f","index":{"url":"z_de1a740d5dc98ffd_ranking_py.html","file":"scripts/ranking.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":147,"n_excluded":9,"n_missing":21,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_de1a740d5dc98ffd_research_py":{"hash":"f70f5afdad459e2a82b06d76961b0502","index":{"url":"z_de1a740d5dc98ffd_research_py.html","file":"scripts/research.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":130,"n_excluded":2,"n_missing":45,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_de1a740d5dc98ffd_setup_py":{"hash":"2b936e494283c91a1b0c1ac177ca3d23","index":{"url":"z_de1a740d5dc98ffd_setup_py.html","file":"scripts/setup.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":168,"n_excluded":2,"n_missing":124,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_de1a740d5dc98ffd_stocks_py":{"hash":"a631cf0e894b87b0a89f70f06987e155","index":{"url":"z_de1a740d5dc98ffd_stocks_py.html","file":"scripts/stocks.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":184,"n_excluded":2,"n_missing":87,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_de1a740d5dc98ffd_summarize_py":{"hash":"d04f1c3e26fe60ac2710db72a49b8e21","index":{"url":"z_de1a740d5dc98ffd_summarize_py.html","file":"scripts/summarize.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":972,"n_excluded":2,"n_missing":462,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_de1a740d5dc98ffd_translate_portfolio_py":{"hash":"74687196bc47c7bcc8dd5ef4e7a118d2","index":{"url":"z_de1a740d5dc98ffd_translate_portfolio_py.html","file":"scripts/translate_portfolio.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":88,"n_excluded":2,"n_missing":88,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_de1a740d5dc98ffd_utils_py":{"hash":"fd9700472399838a648d2182ce916cd4","index":{"url":"z_de1a740d5dc98ffd_utils_py.html","file":"scripts/utils.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":34,"n_excluded":0,"n_missing":10,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}}}}
|
||||
389
htmlcov/style_cb_9ff733b0.css
generated
Normal file
389
htmlcov/style_cb_9ff733b0.css
generated
Normal file
@@ -0,0 +1,389 @@
|
||||
@charset "UTF-8";
|
||||
/* Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 */
|
||||
/* For details: https://github.com/coveragepy/coveragepy/blob/main/NOTICE.txt */
|
||||
/* Don't edit this .css file. Edit the .scss file instead! */
|
||||
html, body, h1, h2, h3, p, table, td, th { margin: 0; padding: 0; border: 0; font-weight: inherit; font-style: inherit; font-size: 100%; font-family: inherit; vertical-align: baseline; }
|
||||
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; font-size: 1em; background: #fff; color: #000; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { body { background: #1e1e1e; } }
|
||||
|
||||
@media (prefers-color-scheme: dark) { body { color: #eee; } }
|
||||
|
||||
html > body { font-size: 16px; }
|
||||
|
||||
a:active, a:focus { outline: 2px dashed #007acc; }
|
||||
|
||||
p { font-size: .875em; line-height: 1.4em; }
|
||||
|
||||
table { border-collapse: collapse; }
|
||||
|
||||
td { vertical-align: top; }
|
||||
|
||||
table tr.hidden { display: none !important; }
|
||||
|
||||
p#no_rows { display: none; font-size: 1.15em; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; }
|
||||
|
||||
a.nav { text-decoration: none; color: inherit; }
|
||||
|
||||
a.nav:hover { text-decoration: underline; color: inherit; }
|
||||
|
||||
.hidden { display: none; }
|
||||
|
||||
header { background: #f8f8f8; width: 100%; z-index: 2; border-bottom: 1px solid #ccc; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { header { background: black; } }
|
||||
|
||||
@media (prefers-color-scheme: dark) { header { border-color: #333; } }
|
||||
|
||||
header .content { padding: 1rem 3.5rem; }
|
||||
|
||||
header h2 { margin-top: .5em; font-size: 1em; }
|
||||
|
||||
header h2 a.button { font-family: inherit; font-size: inherit; border: 1px solid; border-radius: .2em; background: #eee; color: inherit; text-decoration: none; padding: .1em .5em; margin: 1px calc(.1em + 1px); cursor: pointer; border-color: #ccc; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { header h2 a.button { background: #333; } }
|
||||
|
||||
@media (prefers-color-scheme: dark) { header h2 a.button { border-color: #444; } }
|
||||
|
||||
header h2 a.button.current { border: 2px solid; background: #fff; border-color: #999; cursor: default; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { header h2 a.button.current { background: #1e1e1e; } }
|
||||
|
||||
@media (prefers-color-scheme: dark) { header h2 a.button.current { border-color: #777; } }
|
||||
|
||||
header p.text { margin: .5em 0 -.5em; color: #666; font-style: italic; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { header p.text { color: #aaa; } }
|
||||
|
||||
header.sticky { position: fixed; left: 0; right: 0; height: 2.5em; }
|
||||
|
||||
header.sticky .text { display: none; }
|
||||
|
||||
header.sticky h1, header.sticky h2 { font-size: 1em; margin-top: 0; display: inline-block; }
|
||||
|
||||
header.sticky .content { padding: 0.5rem 3.5rem; }
|
||||
|
||||
header.sticky .content p { font-size: 1em; }
|
||||
|
||||
header.sticky ~ #source { padding-top: 6.5em; }
|
||||
|
||||
main { position: relative; z-index: 1; }
|
||||
|
||||
footer { margin: 1rem 3.5rem; }
|
||||
|
||||
footer .content { padding: 0; color: #666; font-style: italic; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { footer .content { color: #aaa; } }
|
||||
|
||||
#index { margin: 1rem 0 0 3.5rem; }
|
||||
|
||||
h1 { font-size: 1.25em; display: inline-block; }
|
||||
|
||||
#filter_container { float: right; margin: 0 2em 0 0; line-height: 1.66em; }
|
||||
|
||||
#filter_container #filter { width: 10em; padding: 0.2em 0.5em; border: 2px solid #ccc; background: #fff; color: #000; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #filter_container #filter { border-color: #444; } }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #filter_container #filter { background: #1e1e1e; } }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #filter_container #filter { color: #eee; } }
|
||||
|
||||
#filter_container #filter:focus { border-color: #007acc; }
|
||||
|
||||
#filter_container :disabled ~ label { color: #ccc; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #filter_container :disabled ~ label { color: #444; } }
|
||||
|
||||
#filter_container label { font-size: .875em; color: #666; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #filter_container label { color: #aaa; } }
|
||||
|
||||
header button { font-family: inherit; font-size: inherit; border: 1px solid; border-radius: .2em; background: #eee; color: inherit; text-decoration: none; padding: .1em .5em; margin: 1px calc(.1em + 1px); cursor: pointer; border-color: #ccc; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { header button { background: #333; } }
|
||||
|
||||
@media (prefers-color-scheme: dark) { header button { border-color: #444; } }
|
||||
|
||||
header button:active, header button:focus { outline: 2px dashed #007acc; }
|
||||
|
||||
header button.run { background: #eeffee; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { header button.run { background: #373d29; } }
|
||||
|
||||
header button.run.show_run { background: #dfd; border: 2px solid #00dd00; margin: 0 .1em; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { header button.run.show_run { background: #373d29; } }
|
||||
|
||||
header button.mis { background: #ffeeee; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { header button.mis { background: #4b1818; } }
|
||||
|
||||
header button.mis.show_mis { background: #fdd; border: 2px solid #ff0000; margin: 0 .1em; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { header button.mis.show_mis { background: #4b1818; } }
|
||||
|
||||
header button.exc { background: #f7f7f7; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { header button.exc { background: #333; } }
|
||||
|
||||
header button.exc.show_exc { background: #eee; border: 2px solid #808080; margin: 0 .1em; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { header button.exc.show_exc { background: #333; } }
|
||||
|
||||
header button.par { background: #ffffd5; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { header button.par { background: #650; } }
|
||||
|
||||
header button.par.show_par { background: #ffa; border: 2px solid #bbbb00; margin: 0 .1em; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { header button.par.show_par { background: #650; } }
|
||||
|
||||
#help_panel, #source p .annotate.long { display: none; position: absolute; z-index: 999; background: #ffffcc; border: 1px solid #888; border-radius: .2em; color: #333; padding: .25em .5em; }
|
||||
|
||||
#source p .annotate.long { white-space: normal; float: right; top: 1.75em; right: 1em; height: auto; }
|
||||
|
||||
#help_panel_wrapper { float: right; position: relative; }
|
||||
|
||||
#keyboard_icon { margin: 5px; }
|
||||
|
||||
#help_panel_state { display: none; }
|
||||
|
||||
#help_panel { top: 25px; right: 0; padding: .75em; border: 1px solid #883; color: #333; }
|
||||
|
||||
#help_panel .keyhelp p { margin-top: .75em; }
|
||||
|
||||
#help_panel .legend { font-style: italic; margin-bottom: 1em; }
|
||||
|
||||
.indexfile #help_panel { width: 25em; }
|
||||
|
||||
.pyfile #help_panel { width: 18em; }
|
||||
|
||||
#help_panel_state:checked ~ #help_panel { display: block; }
|
||||
|
||||
kbd { border: 1px solid black; border-color: #888 #333 #333 #888; padding: .1em .35em; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-weight: bold; background: #eee; border-radius: 3px; }
|
||||
|
||||
#source { padding: 1em 0 1em 3.5rem; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; }
|
||||
|
||||
#source p { position: relative; white-space: pre; }
|
||||
|
||||
#source p * { box-sizing: border-box; }
|
||||
|
||||
#source p .n { float: left; text-align: right; width: 3.5rem; box-sizing: border-box; margin-left: -3.5rem; padding-right: 1em; color: #999; user-select: none; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #source p .n { color: #777; } }
|
||||
|
||||
#source p .n.highlight { background: #ffdd00; }
|
||||
|
||||
#source p .n a { scroll-margin-top: 6em; text-decoration: none; color: #999; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #source p .n a { color: #777; } }
|
||||
|
||||
#source p .n a:hover { text-decoration: underline; color: #999; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #source p .n a:hover { color: #777; } }
|
||||
|
||||
#source p .t { display: inline-block; width: 100%; box-sizing: border-box; margin-left: -.5em; padding-left: 0.3em; border-left: 0.2em solid #fff; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #source p .t { border-color: #1e1e1e; } }
|
||||
|
||||
#source p .t:hover { background: #f2f2f2; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #source p .t:hover { background: #282828; } }
|
||||
|
||||
#source p .t:hover ~ .r .annotate.long { display: block; }
|
||||
|
||||
#source p .t .com { color: #008000; font-style: italic; line-height: 1px; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #source p .t .com { color: #6a9955; } }
|
||||
|
||||
#source p .t .key { font-weight: bold; line-height: 1px; }
|
||||
|
||||
#source p .t .str, #source p .t .fst { color: #0451a5; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #source p .t .str, #source p .t .fst { color: #9cdcfe; } }
|
||||
|
||||
#source p.mis .t { border-left: 0.2em solid #ff0000; }
|
||||
|
||||
#source p.mis.show_mis .t { background: #fdd; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #source p.mis.show_mis .t { background: #4b1818; } }
|
||||
|
||||
#source p.mis.show_mis .t:hover { background: #f2d2d2; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #source p.mis.show_mis .t:hover { background: #532323; } }
|
||||
|
||||
#source p.mis.mis2 .t { border-left: 0.2em dotted #ff0000; }
|
||||
|
||||
#source p.mis.mis2.show_mis .t { background: #ffeeee; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #source p.mis.mis2.show_mis .t { background: #351b1b; } }
|
||||
|
||||
#source p.mis.mis2.show_mis .t:hover { background: #f2d2d2; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #source p.mis.mis2.show_mis .t:hover { background: #532323; } }
|
||||
|
||||
#source p.run .t { border-left: 0.2em solid #00dd00; }
|
||||
|
||||
#source p.run.show_run .t { background: #dfd; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #source p.run.show_run .t { background: #373d29; } }
|
||||
|
||||
#source p.run.show_run .t:hover { background: #d2f2d2; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #source p.run.show_run .t:hover { background: #404633; } }
|
||||
|
||||
#source p.run.run2 .t { border-left: 0.2em dotted #00dd00; }
|
||||
|
||||
#source p.run.run2.show_run .t { background: #eeffee; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #source p.run.run2.show_run .t { background: #2b2e24; } }
|
||||
|
||||
#source p.run.run2.show_run .t:hover { background: #d2f2d2; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #source p.run.run2.show_run .t:hover { background: #404633; } }
|
||||
|
||||
#source p.exc .t { border-left: 0.2em solid #808080; }
|
||||
|
||||
#source p.exc.show_exc .t { background: #eee; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #source p.exc.show_exc .t { background: #333; } }
|
||||
|
||||
#source p.exc.show_exc .t:hover { background: #e2e2e2; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #source p.exc.show_exc .t:hover { background: #3c3c3c; } }
|
||||
|
||||
#source p.exc.exc2 .t { border-left: 0.2em dotted #808080; }
|
||||
|
||||
#source p.exc.exc2.show_exc .t { background: #f7f7f7; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #source p.exc.exc2.show_exc .t { background: #292929; } }
|
||||
|
||||
#source p.exc.exc2.show_exc .t:hover { background: #e2e2e2; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #source p.exc.exc2.show_exc .t:hover { background: #3c3c3c; } }
|
||||
|
||||
#source p.par .t { border-left: 0.2em solid #bbbb00; }
|
||||
|
||||
#source p.par.show_par .t { background: #ffa; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #source p.par.show_par .t { background: #650; } }
|
||||
|
||||
#source p.par.show_par .t:hover { background: #f2f2a2; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #source p.par.show_par .t:hover { background: #6d5d0c; } }
|
||||
|
||||
#source p.par.par2 .t { border-left: 0.2em dotted #bbbb00; }
|
||||
|
||||
#source p.par.par2.show_par .t { background: #ffffd5; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #source p.par.par2.show_par .t { background: #423a0f; } }
|
||||
|
||||
#source p.par.par2.show_par .t:hover { background: #f2f2a2; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #source p.par.par2.show_par .t:hover { background: #6d5d0c; } }
|
||||
|
||||
#source p .r { position: absolute; top: 0; right: 2.5em; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; }
|
||||
|
||||
#source p .annotate { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; color: #666; padding-right: .5em; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #source p .annotate { color: #ddd; } }
|
||||
|
||||
#source p .annotate.short:hover ~ .long { display: block; }
|
||||
|
||||
#source p .annotate.long { width: 30em; right: 2.5em; }
|
||||
|
||||
#source p input { display: none; }
|
||||
|
||||
#source p input ~ .r label.ctx { cursor: pointer; border-radius: .25em; }
|
||||
|
||||
#source p input ~ .r label.ctx::before { content: "▶ "; }
|
||||
|
||||
#source p input ~ .r label.ctx:hover { background: #e8f4ff; color: #666; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #source p input ~ .r label.ctx:hover { background: #0f3a42; } }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #source p input ~ .r label.ctx:hover { color: #aaa; } }
|
||||
|
||||
#source p input:checked ~ .r label.ctx { background: #d0e8ff; color: #666; border-radius: .75em .75em 0 0; padding: 0 .5em; margin: -.25em 0; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #source p input:checked ~ .r label.ctx { background: #056; } }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #source p input:checked ~ .r label.ctx { color: #aaa; } }
|
||||
|
||||
#source p input:checked ~ .r label.ctx::before { content: "▼ "; }
|
||||
|
||||
#source p input:checked ~ .ctxs { padding: .25em .5em; overflow-y: scroll; max-height: 10.5em; }
|
||||
|
||||
#source p label.ctx { color: #999; display: inline-block; padding: 0 .5em; font-size: .8333em; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #source p label.ctx { color: #777; } }
|
||||
|
||||
#source p .ctxs { display: block; max-height: 0; overflow-y: hidden; transition: all .2s; padding: 0 .5em; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; white-space: nowrap; background: #d0e8ff; border-radius: .25em; margin-right: 1.75em; text-align: right; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #source p .ctxs { background: #056; } }
|
||||
|
||||
#index { font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-size: 0.875em; }
|
||||
|
||||
#index table.index { margin-left: -.5em; }
|
||||
|
||||
#index td, #index th { text-align: right; vertical-align: baseline; padding: .25em .5em; border-bottom: 1px solid #eee; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #index td, #index th { border-color: #333; } }
|
||||
|
||||
#index td.name, #index th.name { text-align: left; width: auto; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; min-width: 15em; }
|
||||
|
||||
#index td.left, #index th.left { text-align: left; }
|
||||
|
||||
#index td.spacer, #index th.spacer { border: none; padding: 0; }
|
||||
|
||||
#index td.spacer:hover, #index th.spacer:hover { background: inherit; }
|
||||
|
||||
#index th { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; font-style: italic; color: #333; border-color: #ccc; cursor: pointer; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #index th { color: #ddd; } }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #index th { border-color: #444; } }
|
||||
|
||||
#index th:hover { background: #eee; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #index th:hover { background: #333; } }
|
||||
|
||||
#index th .arrows { color: #666; font-size: 85%; font-family: sans-serif; font-style: normal; pointer-events: none; }
|
||||
|
||||
#index th[aria-sort="ascending"], #index th[aria-sort="descending"] { white-space: nowrap; background: #eee; padding-left: .5em; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #index th[aria-sort="ascending"], #index th[aria-sort="descending"] { background: #333; } }
|
||||
|
||||
#index th[aria-sort="ascending"] .arrows::after { content: " ▲"; }
|
||||
|
||||
#index th[aria-sort="descending"] .arrows::after { content: " ▼"; }
|
||||
|
||||
#index tr.grouphead th { cursor: default; font-style: normal; border-color: #999; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #index tr.grouphead th { border-color: #777; } }
|
||||
|
||||
#index td.name { font-size: 1.15em; }
|
||||
|
||||
#index td.name a { text-decoration: none; color: inherit; }
|
||||
|
||||
#index td.name .no-noun { font-style: italic; }
|
||||
|
||||
#index tr.total td, #index tr.total_dynamic td { font-weight: bold; border-bottom: none; }
|
||||
|
||||
#index tr.region:hover { background: #eee; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #index tr.region:hover { background: #333; } }
|
||||
|
||||
#index tr.region:hover td.name { text-decoration: underline; color: inherit; }
|
||||
|
||||
#scroll_marker { position: fixed; z-index: 3; right: 0; top: 0; width: 16px; height: 100%; background: #fff; border-left: 1px solid #eee; will-change: transform; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #scroll_marker { background: #1e1e1e; } }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #scroll_marker { border-color: #333; } }
|
||||
|
||||
#scroll_marker .marker { background: #ccc; position: absolute; min-height: 3px; width: 100%; }
|
||||
|
||||
@media (prefers-color-scheme: dark) { #scroll_marker .marker { background: #444; } }
|
||||
597
htmlcov/z_de1a740d5dc98ffd_alerts_py.html
generated
Normal file
597
htmlcov/z_de1a740d5dc98ffd_alerts_py.html
generated
Normal file
@@ -0,0 +1,597 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>Coverage for scripts/alerts.py: 60%</title>
|
||||
<link rel="icon" sizes="32x32" href="favicon_32_cb_c827f16f.png">
|
||||
<link rel="stylesheet" href="style_cb_9ff733b0.css" type="text/css">
|
||||
<script src="coverage_html_cb_dd2e7eb5.js" defer></script>
|
||||
</head>
|
||||
<body class="pyfile">
|
||||
<header>
|
||||
<div class="content">
|
||||
<h1>
|
||||
<span class="text">Coverage for </span><b>scripts / alerts.py</b>:
|
||||
<span class="pc_cov">60%</span>
|
||||
</h1>
|
||||
<aside id="help_panel_wrapper">
|
||||
<input id="help_panel_state" type="checkbox">
|
||||
<label for="help_panel_state">
|
||||
<img id="keyboard_icon" src="keybd_closed_cb_900cfef5.png" alt="Show/hide keyboard shortcuts">
|
||||
</label>
|
||||
<div id="help_panel">
|
||||
<p class="legend">Shortcuts on this page</p>
|
||||
<div class="keyhelp">
|
||||
<p>
|
||||
<kbd>r</kbd>
|
||||
<kbd>m</kbd>
|
||||
<kbd>x</kbd>
|
||||
toggle line displays
|
||||
</p>
|
||||
<p>
|
||||
<kbd>j</kbd>
|
||||
<kbd>k</kbd>
|
||||
next/prev highlighted chunk
|
||||
</p>
|
||||
<p>
|
||||
<kbd>0</kbd> (zero) top of page
|
||||
</p>
|
||||
<p>
|
||||
<kbd>1</kbd> (one) first highlighted chunk
|
||||
</p>
|
||||
<p>
|
||||
<kbd>[</kbd>
|
||||
<kbd>]</kbd>
|
||||
prev/next file
|
||||
</p>
|
||||
<p>
|
||||
<kbd>u</kbd> up to the index
|
||||
</p>
|
||||
<p>
|
||||
<kbd>?</kbd> show/hide this help
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
<h2>
|
||||
<span class="text">292 statements </span>
|
||||
<button type="button" class="run button_toggle_run" value="run" data-shortcut="r" title="Toggle lines run">174<span class="text"> run</span></button>
|
||||
<button type="button" class="mis show_mis button_toggle_mis" value="mis" data-shortcut="m" title="Toggle lines missing">118<span class="text"> missing</span></button>
|
||||
<button type="button" class="exc show_exc button_toggle_exc" value="exc" data-shortcut="x" title="Toggle lines excluded">2<span class="text"> excluded</span></button>
|
||||
</h2>
|
||||
<p class="text">
|
||||
<a id="prevFileLink" class="nav" href="index.html">« prev</a>
|
||||
<a id="indexLink" class="nav" href="index.html">^ index</a>
|
||||
<a id="nextFileLink" class="nav" href="z_de1a740d5dc98ffd_briefing_py.html">» next</a>
|
||||
|
||||
<a class="nav" href="https://coverage.readthedocs.io/en/7.13.2">coverage.py v7.13.2</a>,
|
||||
created at 2026-02-01 16:34 -0800
|
||||
</p>
|
||||
<aside class="hidden">
|
||||
<button type="button" class="button_next_chunk" data-shortcut="j"></button>
|
||||
<button type="button" class="button_prev_chunk" data-shortcut="k"></button>
|
||||
<button type="button" class="button_top_of_page" data-shortcut="0"></button>
|
||||
<button type="button" class="button_first_chunk" data-shortcut="1"></button>
|
||||
<button type="button" class="button_prev_file" data-shortcut="["></button>
|
||||
<button type="button" class="button_next_file" data-shortcut="]"></button>
|
||||
<button type="button" class="button_to_index" data-shortcut="u"></button>
|
||||
<button type="button" class="button_show_hide_help" data-shortcut="?"></button>
|
||||
</aside>
|
||||
</div>
|
||||
</header>
|
||||
<main id="source">
|
||||
<p class="pln"><span class="n"><a id="t1" href="#t1">1</a></span><span class="t"><span class="com">#!/usr/bin/env python3</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t2" href="#t2">2</a></span><span class="t"><span class="str">"""</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t3" href="#t3">3</a></span><span class="t"><span class="str">Price Target Alerts - Track buy zone alerts for stocks.</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t4" href="#t4">4</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t5" href="#t5">5</a></span><span class="t"><span class="str">Features:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t6" href="#t6">6</a></span><span class="t"><span class="str">- Set price target alerts (buy zone triggers)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t7" href="#t7">7</a></span><span class="t"><span class="str">- Check alerts against current prices</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t8" href="#t8">8</a></span><span class="t"><span class="str">- Snooze, update, delete alerts</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t9" href="#t9">9</a></span><span class="t"><span class="str">- Multi-currency support (USD, EUR, JPY, SGD, MXN)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t10" href="#t10">10</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t11" href="#t11">11</a></span><span class="t"><span class="str">Usage:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t12" href="#t12">12</a></span><span class="t"><span class="str"> alerts.py list # Show all alerts</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t13" href="#t13">13</a></span><span class="t"><span class="str"> alerts.py set CRWD 400 --note 'Kaufzone' # Set alert</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t14" href="#t14">14</a></span><span class="t"><span class="str"> alerts.py check # Check triggered alerts</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t15" href="#t15">15</a></span><span class="t"><span class="str"> alerts.py delete CRWD # Delete alert</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t16" href="#t16">16</a></span><span class="t"><span class="str"> alerts.py snooze CRWD --days 7 # Snooze for 7 days</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t17" href="#t17">17</a></span><span class="t"><span class="str"> alerts.py update CRWD 380 # Update target price</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t18" href="#t18">18</a></span><span class="t"><span class="str">"""</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t19" href="#t19">19</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t20" href="#t20">20</a></span><span class="t"><span class="key">import</span> <span class="nam">argparse</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t21" href="#t21">21</a></span><span class="t"><span class="key">import</span> <span class="nam">json</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t22" href="#t22">22</a></span><span class="t"><span class="key">import</span> <span class="nam">sys</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t23" href="#t23">23</a></span><span class="t"><span class="key">from</span> <span class="nam">datetime</span> <span class="key">import</span> <span class="nam">datetime</span><span class="op">,</span> <span class="nam">timedelta</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t24" href="#t24">24</a></span><span class="t"><span class="key">from</span> <span class="nam">pathlib</span> <span class="key">import</span> <span class="nam">Path</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t25" href="#t25">25</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t26" href="#t26">26</a></span><span class="t"><span class="key">from</span> <span class="nam">utils</span> <span class="key">import</span> <span class="nam">ensure_venv</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t27" href="#t27">27</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t28" href="#t28">28</a></span><span class="t"><span class="nam">ensure_venv</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t29" href="#t29">29</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t30" href="#t30">30</a></span><span class="t"><span class="com"># Lazy import to avoid numpy issues at module load</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t31" href="#t31">31</a></span><span class="t"><span class="nam">fetch_market_data</span> <span class="op">=</span> <span class="key">None</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t32" href="#t32">32</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t33" href="#t33">33</a></span><span class="t"><span class="key">def</span> <span class="nam">get_fetch_market_data</span><span class="op">(</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t34" href="#t34">34</a></span><span class="t"> <span class="key">global</span> <span class="nam">fetch_market_data</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t35" href="#t35">35</a></span><span class="t"> <span class="key">if</span> <span class="nam">fetch_market_data</span> <span class="key">is</span> <span class="key">None</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t36" href="#t36">36</a></span><span class="t"> <span class="key">from</span> <span class="nam">fetch_news</span> <span class="key">import</span> <span class="nam">fetch_market_data</span> <span class="key">as</span> <span class="nam">fmd</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t37" href="#t37">37</a></span><span class="t"> <span class="nam">fetch_market_data</span> <span class="op">=</span> <span class="nam">fmd</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t38" href="#t38">38</a></span><span class="t"> <span class="key">return</span> <span class="nam">fetch_market_data</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t39" href="#t39">39</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t40" href="#t40">40</a></span><span class="t"><span class="nam">SCRIPT_DIR</span> <span class="op">=</span> <span class="nam">Path</span><span class="op">(</span><span class="nam">__file__</span><span class="op">)</span><span class="op">.</span><span class="nam">parent</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t41" href="#t41">41</a></span><span class="t"><span class="nam">CONFIG_DIR</span> <span class="op">=</span> <span class="nam">SCRIPT_DIR</span><span class="op">.</span><span class="nam">parent</span> <span class="op">/</span> <span class="str">"config"</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t42" href="#t42">42</a></span><span class="t"><span class="nam">ALERTS_FILE</span> <span class="op">=</span> <span class="nam">CONFIG_DIR</span> <span class="op">/</span> <span class="str">"alerts.json"</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t43" href="#t43">43</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t44" href="#t44">44</a></span><span class="t"><span class="nam">SUPPORTED_CURRENCIES</span> <span class="op">=</span> <span class="op">[</span><span class="str">"USD"</span><span class="op">,</span> <span class="str">"EUR"</span><span class="op">,</span> <span class="str">"JPY"</span><span class="op">,</span> <span class="str">"SGD"</span><span class="op">,</span> <span class="str">"MXN"</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t45" href="#t45">45</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t46" href="#t46">46</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t47" href="#t47">47</a></span><span class="t"><span class="key">def</span> <span class="nam">load_alerts</span><span class="op">(</span><span class="op">)</span> <span class="op">-></span> <span class="nam">dict</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t48" href="#t48">48</a></span><span class="t"> <span class="str">"""Load alerts from JSON file."""</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t49" href="#t49">49</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">ALERTS_FILE</span><span class="op">.</span><span class="nam">exists</span><span class="op">(</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t50" href="#t50">50</a></span><span class="t"> <span class="key">return</span> <span class="op">{</span><span class="str">"_meta"</span><span class="op">:</span> <span class="op">{</span><span class="str">"version"</span><span class="op">:</span> <span class="num">1</span><span class="op">,</span> <span class="str">"supported_currencies"</span><span class="op">:</span> <span class="nam">SUPPORTED_CURRENCIES</span><span class="op">}</span><span class="op">,</span> <span class="str">"alerts"</span><span class="op">:</span> <span class="op">[</span><span class="op">]</span><span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t51" href="#t51">51</a></span><span class="t"> <span class="key">return</span> <span class="nam">json</span><span class="op">.</span><span class="nam">loads</span><span class="op">(</span><span class="nam">ALERTS_FILE</span><span class="op">.</span><span class="nam">read_text</span><span class="op">(</span><span class="op">)</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t52" href="#t52">52</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t53" href="#t53">53</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t54" href="#t54">54</a></span><span class="t"><span class="key">def</span> <span class="nam">save_alerts</span><span class="op">(</span><span class="nam">data</span><span class="op">:</span> <span class="nam">dict</span><span class="op">)</span> <span class="op">-></span> <span class="key">None</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t55" href="#t55">55</a></span><span class="t"> <span class="str">"""Save alerts to JSON file."""</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t56" href="#t56">56</a></span><span class="t"> <span class="nam">data</span><span class="op">[</span><span class="str">"_meta"</span><span class="op">]</span><span class="op">[</span><span class="str">"updated_at"</span><span class="op">]</span> <span class="op">=</span> <span class="nam">datetime</span><span class="op">.</span><span class="nam">now</span><span class="op">(</span><span class="op">)</span><span class="op">.</span><span class="nam">isoformat</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t57" href="#t57">57</a></span><span class="t"> <span class="nam">ALERTS_FILE</span><span class="op">.</span><span class="nam">write_text</span><span class="op">(</span><span class="nam">json</span><span class="op">.</span><span class="nam">dumps</span><span class="op">(</span><span class="nam">data</span><span class="op">,</span> <span class="nam">indent</span><span class="op">=</span><span class="num">2</span><span class="op">)</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t58" href="#t58">58</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t59" href="#t59">59</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t60" href="#t60">60</a></span><span class="t"><span class="key">def</span> <span class="nam">get_alert_by_ticker</span><span class="op">(</span><span class="nam">alerts</span><span class="op">:</span> <span class="nam">list</span><span class="op">,</span> <span class="nam">ticker</span><span class="op">:</span> <span class="nam">str</span><span class="op">)</span> <span class="op">-></span> <span class="nam">dict</span> <span class="op">|</span> <span class="key">None</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t61" href="#t61">61</a></span><span class="t"> <span class="str">"""Find alert by ticker."""</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t62" href="#t62">62</a></span><span class="t"> <span class="nam">ticker</span> <span class="op">=</span> <span class="nam">ticker</span><span class="op">.</span><span class="nam">upper</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t63" href="#t63">63</a></span><span class="t"> <span class="key">for</span> <span class="nam">alert</span> <span class="key">in</span> <span class="nam">alerts</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t64" href="#t64">64</a></span><span class="t"> <span class="key">if</span> <span class="nam">alert</span><span class="op">[</span><span class="str">"ticker"</span><span class="op">]</span> <span class="op">==</span> <span class="nam">ticker</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t65" href="#t65">65</a></span><span class="t"> <span class="key">return</span> <span class="nam">alert</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t66" href="#t66">66</a></span><span class="t"> <span class="key">return</span> <span class="key">None</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t67" href="#t67">67</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t68" href="#t68">68</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t69" href="#t69">69</a></span><span class="t"><span class="key">def</span> <span class="nam">format_price</span><span class="op">(</span><span class="nam">price</span><span class="op">:</span> <span class="nam">float</span><span class="op">,</span> <span class="nam">currency</span><span class="op">:</span> <span class="nam">str</span><span class="op">)</span> <span class="op">-></span> <span class="nam">str</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t70" href="#t70">70</a></span><span class="t"> <span class="str">"""Format price with currency symbol."""</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t71" href="#t71">71</a></span><span class="t"> <span class="nam">symbols</span> <span class="op">=</span> <span class="op">{</span><span class="str">"USD"</span><span class="op">:</span> <span class="str">"$"</span><span class="op">,</span> <span class="str">"EUR"</span><span class="op">:</span> <span class="str">"€"</span><span class="op">,</span> <span class="str">"JPY"</span><span class="op">:</span> <span class="str">"¥"</span><span class="op">,</span> <span class="str">"SGD"</span><span class="op">:</span> <span class="str">"S$"</span><span class="op">,</span> <span class="str">"MXN"</span><span class="op">:</span> <span class="str">"MX$"</span><span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t72" href="#t72">72</a></span><span class="t"> <span class="nam">symbol</span> <span class="op">=</span> <span class="nam">symbols</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="nam">currency</span><span class="op">,</span> <span class="nam">currency</span> <span class="op">+</span> <span class="str">" "</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t73" href="#t73">73</a></span><span class="t"> <span class="key">if</span> <span class="nam">currency</span> <span class="op">==</span> <span class="str">"JPY"</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t74" href="#t74">74</a></span><span class="t"> <span class="key">return</span> <span class="str">f"{symbol}{price:,.0f}"</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t75" href="#t75">75</a></span><span class="t"> <span class="key">return</span> <span class="str">f"{symbol}{price:,.2f}"</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t76" href="#t76">76</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t77" href="#t77">77</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t78" href="#t78">78</a></span><span class="t"><span class="key">def</span> <span class="nam">cmd_list</span><span class="op">(</span><span class="nam">args</span><span class="op">)</span> <span class="op">-></span> <span class="key">None</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t79" href="#t79">79</a></span><span class="t"> <span class="str">"""List all alerts."""</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t80" href="#t80">80</a></span><span class="t"> <span class="nam">data</span> <span class="op">=</span> <span class="nam">load_alerts</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t81" href="#t81">81</a></span><span class="t"> <span class="nam">alerts</span> <span class="op">=</span> <span class="nam">data</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"alerts"</span><span class="op">,</span> <span class="op">[</span><span class="op">]</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t82" href="#t82">82</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t83" href="#t83">83</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">alerts</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t84" href="#t84">84</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">"📭 No price alerts set"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t85" href="#t85">85</a></span><span class="t"> <span class="key">return</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t86" href="#t86">86</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t87" href="#t87">87</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"📊 Price Alerts ({len(alerts)} total)\n"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t88" href="#t88">88</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t89" href="#t89">89</a></span><span class="t"> <span class="nam">now</span> <span class="op">=</span> <span class="nam">datetime</span><span class="op">.</span><span class="nam">now</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t90" href="#t90">90</a></span><span class="t"> <span class="nam">active</span> <span class="op">=</span> <span class="op">[</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t91" href="#t91">91</a></span><span class="t"> <span class="nam">snoozed</span> <span class="op">=</span> <span class="op">[</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t92" href="#t92">92</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t93" href="#t93">93</a></span><span class="t"> <span class="key">for</span> <span class="nam">alert</span> <span class="key">in</span> <span class="nam">alerts</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t94" href="#t94">94</a></span><span class="t"> <span class="nam">snooze_until</span> <span class="op">=</span> <span class="nam">alert</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"snooze_until"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t95" href="#t95">95</a></span><span class="t"> <span class="key">if</span> <span class="nam">snooze_until</span> <span class="key">and</span> <span class="nam">datetime</span><span class="op">.</span><span class="nam">fromisoformat</span><span class="op">(</span><span class="nam">snooze_until</span><span class="op">)</span> <span class="op">></span> <span class="nam">now</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t96" href="#t96">96</a></span><span class="t"> <span class="nam">snoozed</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="nam">alert</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t97" href="#t97">97</a></span><span class="t"> <span class="key">else</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t98" href="#t98">98</a></span><span class="t"> <span class="nam">active</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="nam">alert</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t99" href="#t99">99</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t100" href="#t100">100</a></span><span class="t"> <span class="key">if</span> <span class="nam">active</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t101" href="#t101">101</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">"### Active Alerts"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t102" href="#t102">102</a></span><span class="t"> <span class="key">for</span> <span class="nam">a</span> <span class="key">in</span> <span class="nam">active</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t103" href="#t103">103</a></span><span class="t"> <span class="nam">target</span> <span class="op">=</span> <span class="nam">format_price</span><span class="op">(</span><span class="nam">a</span><span class="op">[</span><span class="str">"target_price"</span><span class="op">]</span><span class="op">,</span> <span class="nam">a</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"currency"</span><span class="op">,</span> <span class="str">"USD"</span><span class="op">)</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t104" href="#t104">104</a></span><span class="t"> <span class="nam">note</span> <span class="op">=</span> <span class="str">f' — "{a["note"]}"'</span> <span class="key">if</span> <span class="nam">a</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"note"</span><span class="op">)</span> <span class="key">else</span> <span class="str">""</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t105" href="#t105">105</a></span><span class="t"> <span class="nam">user</span> <span class="op">=</span> <span class="str">f" (by {a['set_by']})"</span> <span class="key">if</span> <span class="nam">a</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"set_by"</span><span class="op">)</span> <span class="key">else</span> <span class="str">""</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t106" href="#t106">106</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f" • {a['ticker']}: {target}{note}{user}"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t107" href="#t107">107</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t108" href="#t108">108</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t109" href="#t109">109</a></span><span class="t"> <span class="key">if</span> <span class="nam">snoozed</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t110" href="#t110">110</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">"### Snoozed"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t111" href="#t111">111</a></span><span class="t"> <span class="key">for</span> <span class="nam">a</span> <span class="key">in</span> <span class="nam">snoozed</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t112" href="#t112">112</a></span><span class="t"> <span class="nam">target</span> <span class="op">=</span> <span class="nam">format_price</span><span class="op">(</span><span class="nam">a</span><span class="op">[</span><span class="str">"target_price"</span><span class="op">]</span><span class="op">,</span> <span class="nam">a</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"currency"</span><span class="op">,</span> <span class="str">"USD"</span><span class="op">)</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t113" href="#t113">113</a></span><span class="t"> <span class="nam">until</span> <span class="op">=</span> <span class="nam">datetime</span><span class="op">.</span><span class="nam">fromisoformat</span><span class="op">(</span><span class="nam">a</span><span class="op">[</span><span class="str">"snooze_until"</span><span class="op">]</span><span class="op">)</span><span class="op">.</span><span class="nam">strftime</span><span class="op">(</span><span class="str">"%Y-%m-%d"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t114" href="#t114">114</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f" • {a['ticker']}: {target} (until {until})"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t115" href="#t115">115</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t116" href="#t116">116</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t117" href="#t117">117</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t118" href="#t118">118</a></span><span class="t"><span class="key">def</span> <span class="nam">cmd_set</span><span class="op">(</span><span class="nam">args</span><span class="op">)</span> <span class="op">-></span> <span class="key">None</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t119" href="#t119">119</a></span><span class="t"> <span class="str">"""Set a new alert."""</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t120" href="#t120">120</a></span><span class="t"> <span class="nam">data</span> <span class="op">=</span> <span class="nam">load_alerts</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t121" href="#t121">121</a></span><span class="t"> <span class="nam">alerts</span> <span class="op">=</span> <span class="nam">data</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"alerts"</span><span class="op">,</span> <span class="op">[</span><span class="op">]</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t122" href="#t122">122</a></span><span class="t"> <span class="nam">ticker</span> <span class="op">=</span> <span class="nam">args</span><span class="op">.</span><span class="nam">ticker</span><span class="op">.</span><span class="nam">upper</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t123" href="#t123">123</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t124" href="#t124">124</a></span><span class="t"> <span class="com"># Check if alert exists</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t125" href="#t125">125</a></span><span class="t"> <span class="nam">existing</span> <span class="op">=</span> <span class="nam">get_alert_by_ticker</span><span class="op">(</span><span class="nam">alerts</span><span class="op">,</span> <span class="nam">ticker</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t126" href="#t126">126</a></span><span class="t"> <span class="key">if</span> <span class="nam">existing</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t127" href="#t127">127</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"⚠️ Alert for {ticker} already exists. Use 'update' to change target."</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t128" href="#t128">128</a></span><span class="t"> <span class="key">return</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t129" href="#t129">129</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t130" href="#t130">130</a></span><span class="t"> <span class="com"># Validate target price</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t131" href="#t131">131</a></span><span class="t"> <span class="key">if</span> <span class="nam">args</span><span class="op">.</span><span class="nam">target</span> <span class="op"><=</span> <span class="num">0</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t132" href="#t132">132</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"❌ Target price must be greater than 0"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t133" href="#t133">133</a></span><span class="t"> <span class="key">return</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t134" href="#t134">134</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t135" href="#t135">135</a></span><span class="t"> <span class="nam">currency</span> <span class="op">=</span> <span class="nam">args</span><span class="op">.</span><span class="nam">currency</span><span class="op">.</span><span class="nam">upper</span><span class="op">(</span><span class="op">)</span> <span class="key">if</span> <span class="nam">args</span><span class="op">.</span><span class="nam">currency</span> <span class="key">else</span> <span class="str">"USD"</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t136" href="#t136">136</a></span><span class="t"> <span class="key">if</span> <span class="nam">currency</span> <span class="key">not</span> <span class="key">in</span> <span class="nam">SUPPORTED_CURRENCIES</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t137" href="#t137">137</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"❌ Currency {currency} not supported. Use: {', '.join(SUPPORTED_CURRENCIES)}"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t138" href="#t138">138</a></span><span class="t"> <span class="key">return</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t139" href="#t139">139</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t140" href="#t140">140</a></span><span class="t"> <span class="com"># Warn about currency mismatch based on ticker suffix</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t141" href="#t141">141</a></span><span class="t"> <span class="nam">ticker_currency_map</span> <span class="op">=</span> <span class="op">{</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t142" href="#t142">142</a></span><span class="t"> <span class="str">".T"</span><span class="op">:</span> <span class="str">"JPY"</span><span class="op">,</span> <span class="com"># Tokyo</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t143" href="#t143">143</a></span><span class="t"> <span class="str">".SI"</span><span class="op">:</span> <span class="str">"SGD"</span><span class="op">,</span> <span class="com"># Singapore</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t144" href="#t144">144</a></span><span class="t"> <span class="str">".MX"</span><span class="op">:</span> <span class="str">"MXN"</span><span class="op">,</span> <span class="com"># Mexico</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t145" href="#t145">145</a></span><span class="t"> <span class="str">".DE"</span><span class="op">:</span> <span class="str">"EUR"</span><span class="op">,</span> <span class="str">".F"</span><span class="op">:</span> <span class="str">"EUR"</span><span class="op">,</span> <span class="str">".PA"</span><span class="op">:</span> <span class="str">"EUR"</span><span class="op">,</span> <span class="com"># Europe</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t146" href="#t146">146</a></span><span class="t"> <span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t147" href="#t147">147</a></span><span class="t"> <span class="nam">expected_currency</span> <span class="op">=</span> <span class="str">"USD"</span> <span class="com"># Default for US stocks</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t148" href="#t148">148</a></span><span class="t"> <span class="key">for</span> <span class="nam">suffix</span><span class="op">,</span> <span class="nam">curr</span> <span class="key">in</span> <span class="nam">ticker_currency_map</span><span class="op">.</span><span class="nam">items</span><span class="op">(</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t149" href="#t149">149</a></span><span class="t"> <span class="key">if</span> <span class="nam">ticker</span><span class="op">.</span><span class="nam">endswith</span><span class="op">(</span><span class="nam">suffix</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t150" href="#t150">150</a></span><span class="t"> <span class="nam">expected_currency</span> <span class="op">=</span> <span class="nam">curr</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t151" href="#t151">151</a></span><span class="t"> <span class="key">break</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t152" href="#t152">152</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t153" href="#t153">153</a></span><span class="t"> <span class="key">if</span> <span class="nam">currency</span> <span class="op">!=</span> <span class="nam">expected_currency</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t154" href="#t154">154</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"⚠️ Warning: {ticker} trades in {expected_currency}, but alert set in {currency}"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t155" href="#t155">155</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t156" href="#t156">156</a></span><span class="t"> <span class="com"># Fetch current price (optional - may fail if numpy broken)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t157" href="#t157">157</a></span><span class="t"> <span class="nam">current_price</span> <span class="op">=</span> <span class="key">None</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t158" href="#t158">158</a></span><span class="t"> <span class="key">try</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t159" href="#t159">159</a></span><span class="t"> <span class="nam">quotes</span> <span class="op">=</span> <span class="nam">get_fetch_market_data</span><span class="op">(</span><span class="op">)</span><span class="op">(</span><span class="op">[</span><span class="nam">ticker</span><span class="op">]</span><span class="op">,</span> <span class="nam">timeout</span><span class="op">=</span><span class="num">10</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t160" href="#t160">160</a></span><span class="t"> <span class="key">if</span> <span class="nam">ticker</span> <span class="key">in</span> <span class="nam">quotes</span> <span class="key">and</span> <span class="nam">quotes</span><span class="op">[</span><span class="nam">ticker</span><span class="op">]</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"price"</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t161" href="#t161">161</a></span><span class="t"> <span class="nam">current_price</span> <span class="op">=</span> <span class="nam">quotes</span><span class="op">[</span><span class="nam">ticker</span><span class="op">]</span><span class="op">[</span><span class="str">"price"</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t162" href="#t162">162</a></span><span class="t"> <span class="key">except</span> <span class="nam">Exception</span> <span class="key">as</span> <span class="nam">e</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t163" href="#t163">163</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"⚠️ Could not fetch current price: {e}"</span><span class="op">,</span> <span class="nam">file</span><span class="op">=</span><span class="nam">sys</span><span class="op">.</span><span class="nam">stderr</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t164" href="#t164">164</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t165" href="#t165">165</a></span><span class="t"> <span class="nam">alert</span> <span class="op">=</span> <span class="op">{</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t166" href="#t166">166</a></span><span class="t"> <span class="str">"ticker"</span><span class="op">:</span> <span class="nam">ticker</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t167" href="#t167">167</a></span><span class="t"> <span class="str">"target_price"</span><span class="op">:</span> <span class="nam">args</span><span class="op">.</span><span class="nam">target</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t168" href="#t168">168</a></span><span class="t"> <span class="str">"currency"</span><span class="op">:</span> <span class="nam">currency</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t169" href="#t169">169</a></span><span class="t"> <span class="str">"note"</span><span class="op">:</span> <span class="nam">args</span><span class="op">.</span><span class="nam">note</span> <span class="key">or</span> <span class="str">""</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t170" href="#t170">170</a></span><span class="t"> <span class="str">"set_by"</span><span class="op">:</span> <span class="nam">args</span><span class="op">.</span><span class="nam">user</span> <span class="key">or</span> <span class="str">""</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t171" href="#t171">171</a></span><span class="t"> <span class="str">"set_date"</span><span class="op">:</span> <span class="nam">datetime</span><span class="op">.</span><span class="nam">now</span><span class="op">(</span><span class="op">)</span><span class="op">.</span><span class="nam">strftime</span><span class="op">(</span><span class="str">"%Y-%m-%d"</span><span class="op">)</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t172" href="#t172">172</a></span><span class="t"> <span class="str">"status"</span><span class="op">:</span> <span class="str">"active"</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t173" href="#t173">173</a></span><span class="t"> <span class="str">"snooze_until"</span><span class="op">:</span> <span class="key">None</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t174" href="#t174">174</a></span><span class="t"> <span class="str">"triggered_count"</span><span class="op">:</span> <span class="num">0</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t175" href="#t175">175</a></span><span class="t"> <span class="str">"last_triggered"</span><span class="op">:</span> <span class="key">None</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t176" href="#t176">176</a></span><span class="t"> <span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t177" href="#t177">177</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t178" href="#t178">178</a></span><span class="t"> <span class="nam">alerts</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="nam">alert</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t179" href="#t179">179</a></span><span class="t"> <span class="nam">data</span><span class="op">[</span><span class="str">"alerts"</span><span class="op">]</span> <span class="op">=</span> <span class="nam">alerts</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t180" href="#t180">180</a></span><span class="t"> <span class="nam">save_alerts</span><span class="op">(</span><span class="nam">data</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t181" href="#t181">181</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t182" href="#t182">182</a></span><span class="t"> <span class="nam">target_str</span> <span class="op">=</span> <span class="nam">format_price</span><span class="op">(</span><span class="nam">args</span><span class="op">.</span><span class="nam">target</span><span class="op">,</span> <span class="nam">currency</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t183" href="#t183">183</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"✅ Alert set: {ticker} under {target_str}"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t184" href="#t184">184</a></span><span class="t"> <span class="key">if</span> <span class="nam">current_price</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t185" href="#t185">185</a></span><span class="t"> <span class="nam">pct_diff</span> <span class="op">=</span> <span class="op">(</span><span class="op">(</span><span class="nam">current_price</span> <span class="op">-</span> <span class="nam">args</span><span class="op">.</span><span class="nam">target</span><span class="op">)</span> <span class="op">/</span> <span class="nam">current_price</span><span class="op">)</span> <span class="op">*</span> <span class="num">100</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t186" href="#t186">186</a></span><span class="t"> <span class="nam">current_str</span> <span class="op">=</span> <span class="nam">format_price</span><span class="op">(</span><span class="nam">current_price</span><span class="op">,</span> <span class="nam">currency</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t187" href="#t187">187</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f" Current: {current_str} ({pct_diff:+.1f}% to target)"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t188" href="#t188">188</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t189" href="#t189">189</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t190" href="#t190">190</a></span><span class="t"><span class="key">def</span> <span class="nam">cmd_delete</span><span class="op">(</span><span class="nam">args</span><span class="op">)</span> <span class="op">-></span> <span class="key">None</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t191" href="#t191">191</a></span><span class="t"> <span class="str">"""Delete an alert."""</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t192" href="#t192">192</a></span><span class="t"> <span class="nam">data</span> <span class="op">=</span> <span class="nam">load_alerts</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t193" href="#t193">193</a></span><span class="t"> <span class="nam">alerts</span> <span class="op">=</span> <span class="nam">data</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"alerts"</span><span class="op">,</span> <span class="op">[</span><span class="op">]</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t194" href="#t194">194</a></span><span class="t"> <span class="nam">ticker</span> <span class="op">=</span> <span class="nam">args</span><span class="op">.</span><span class="nam">ticker</span><span class="op">.</span><span class="nam">upper</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t195" href="#t195">195</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t196" href="#t196">196</a></span><span class="t"> <span class="nam">new_alerts</span> <span class="op">=</span> <span class="op">[</span><span class="nam">a</span> <span class="key">for</span> <span class="nam">a</span> <span class="key">in</span> <span class="nam">alerts</span> <span class="key">if</span> <span class="nam">a</span><span class="op">[</span><span class="str">"ticker"</span><span class="op">]</span> <span class="op">!=</span> <span class="nam">ticker</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t197" href="#t197">197</a></span><span class="t"> <span class="key">if</span> <span class="nam">len</span><span class="op">(</span><span class="nam">new_alerts</span><span class="op">)</span> <span class="op">==</span> <span class="nam">len</span><span class="op">(</span><span class="nam">alerts</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t198" href="#t198">198</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"❌ No alert found for {ticker}"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t199" href="#t199">199</a></span><span class="t"> <span class="key">return</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t200" href="#t200">200</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t201" href="#t201">201</a></span><span class="t"> <span class="nam">data</span><span class="op">[</span><span class="str">"alerts"</span><span class="op">]</span> <span class="op">=</span> <span class="nam">new_alerts</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t202" href="#t202">202</a></span><span class="t"> <span class="nam">save_alerts</span><span class="op">(</span><span class="nam">data</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t203" href="#t203">203</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"🗑️ Alert deleted: {ticker}"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t204" href="#t204">204</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t205" href="#t205">205</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t206" href="#t206">206</a></span><span class="t"><span class="key">def</span> <span class="nam">cmd_snooze</span><span class="op">(</span><span class="nam">args</span><span class="op">)</span> <span class="op">-></span> <span class="key">None</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t207" href="#t207">207</a></span><span class="t"> <span class="str">"""Snooze an alert."""</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t208" href="#t208">208</a></span><span class="t"> <span class="nam">data</span> <span class="op">=</span> <span class="nam">load_alerts</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t209" href="#t209">209</a></span><span class="t"> <span class="nam">alerts</span> <span class="op">=</span> <span class="nam">data</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"alerts"</span><span class="op">,</span> <span class="op">[</span><span class="op">]</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t210" href="#t210">210</a></span><span class="t"> <span class="nam">ticker</span> <span class="op">=</span> <span class="nam">args</span><span class="op">.</span><span class="nam">ticker</span><span class="op">.</span><span class="nam">upper</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t211" href="#t211">211</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t212" href="#t212">212</a></span><span class="t"> <span class="nam">alert</span> <span class="op">=</span> <span class="nam">get_alert_by_ticker</span><span class="op">(</span><span class="nam">alerts</span><span class="op">,</span> <span class="nam">ticker</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t213" href="#t213">213</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">alert</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t214" href="#t214">214</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"❌ No alert found for {ticker}"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t215" href="#t215">215</a></span><span class="t"> <span class="key">return</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t216" href="#t216">216</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t217" href="#t217">217</a></span><span class="t"> <span class="nam">days</span> <span class="op">=</span> <span class="nam">args</span><span class="op">.</span><span class="nam">days</span> <span class="key">or</span> <span class="num">7</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t218" href="#t218">218</a></span><span class="t"> <span class="nam">snooze_until</span> <span class="op">=</span> <span class="nam">datetime</span><span class="op">.</span><span class="nam">now</span><span class="op">(</span><span class="op">)</span> <span class="op">+</span> <span class="nam">timedelta</span><span class="op">(</span><span class="nam">days</span><span class="op">=</span><span class="nam">days</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t219" href="#t219">219</a></span><span class="t"> <span class="nam">alert</span><span class="op">[</span><span class="str">"snooze_until"</span><span class="op">]</span> <span class="op">=</span> <span class="nam">snooze_until</span><span class="op">.</span><span class="nam">isoformat</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t220" href="#t220">220</a></span><span class="t"> <span class="nam">save_alerts</span><span class="op">(</span><span class="nam">data</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t221" href="#t221">221</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"😴 Alert snoozed: {ticker} until {snooze_until.strftime('%Y-%m-%d')}"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t222" href="#t222">222</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t223" href="#t223">223</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t224" href="#t224">224</a></span><span class="t"><span class="key">def</span> <span class="nam">cmd_update</span><span class="op">(</span><span class="nam">args</span><span class="op">)</span> <span class="op">-></span> <span class="key">None</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t225" href="#t225">225</a></span><span class="t"> <span class="str">"""Update alert target price."""</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t226" href="#t226">226</a></span><span class="t"> <span class="nam">data</span> <span class="op">=</span> <span class="nam">load_alerts</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t227" href="#t227">227</a></span><span class="t"> <span class="nam">alerts</span> <span class="op">=</span> <span class="nam">data</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"alerts"</span><span class="op">,</span> <span class="op">[</span><span class="op">]</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t228" href="#t228">228</a></span><span class="t"> <span class="nam">ticker</span> <span class="op">=</span> <span class="nam">args</span><span class="op">.</span><span class="nam">ticker</span><span class="op">.</span><span class="nam">upper</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t229" href="#t229">229</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t230" href="#t230">230</a></span><span class="t"> <span class="nam">alert</span> <span class="op">=</span> <span class="nam">get_alert_by_ticker</span><span class="op">(</span><span class="nam">alerts</span><span class="op">,</span> <span class="nam">ticker</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t231" href="#t231">231</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">alert</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t232" href="#t232">232</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"❌ No alert found for {ticker}"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t233" href="#t233">233</a></span><span class="t"> <span class="key">return</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t234" href="#t234">234</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t235" href="#t235">235</a></span><span class="t"> <span class="com"># Validate target price</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t236" href="#t236">236</a></span><span class="t"> <span class="key">if</span> <span class="nam">args</span><span class="op">.</span><span class="nam">target</span> <span class="op"><=</span> <span class="num">0</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t237" href="#t237">237</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"❌ Target price must be greater than 0"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t238" href="#t238">238</a></span><span class="t"> <span class="key">return</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t239" href="#t239">239</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t240" href="#t240">240</a></span><span class="t"> <span class="nam">old_target</span> <span class="op">=</span> <span class="nam">alert</span><span class="op">[</span><span class="str">"target_price"</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t241" href="#t241">241</a></span><span class="t"> <span class="nam">alert</span><span class="op">[</span><span class="str">"target_price"</span><span class="op">]</span> <span class="op">=</span> <span class="nam">args</span><span class="op">.</span><span class="nam">target</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t242" href="#t242">242</a></span><span class="t"> <span class="key">if</span> <span class="nam">args</span><span class="op">.</span><span class="nam">note</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t243" href="#t243">243</a></span><span class="t"> <span class="nam">alert</span><span class="op">[</span><span class="str">"note"</span><span class="op">]</span> <span class="op">=</span> <span class="nam">args</span><span class="op">.</span><span class="nam">note</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t244" href="#t244">244</a></span><span class="t"> <span class="nam">save_alerts</span><span class="op">(</span><span class="nam">data</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t245" href="#t245">245</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t246" href="#t246">246</a></span><span class="t"> <span class="nam">currency</span> <span class="op">=</span> <span class="nam">alert</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"currency"</span><span class="op">,</span> <span class="str">"USD"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t247" href="#t247">247</a></span><span class="t"> <span class="nam">old_str</span> <span class="op">=</span> <span class="nam">format_price</span><span class="op">(</span><span class="nam">old_target</span><span class="op">,</span> <span class="nam">currency</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t248" href="#t248">248</a></span><span class="t"> <span class="nam">new_str</span> <span class="op">=</span> <span class="nam">format_price</span><span class="op">(</span><span class="nam">args</span><span class="op">.</span><span class="nam">target</span><span class="op">,</span> <span class="nam">currency</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t249" href="#t249">249</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"✏️ Alert updated: {ticker} {old_str} → {new_str}"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t250" href="#t250">250</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t251" href="#t251">251</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t252" href="#t252">252</a></span><span class="t"><span class="key">def</span> <span class="nam">cmd_check</span><span class="op">(</span><span class="nam">args</span><span class="op">)</span> <span class="op">-></span> <span class="key">None</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t253" href="#t253">253</a></span><span class="t"> <span class="str">"""Check alerts against current prices."""</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t254" href="#t254">254</a></span><span class="t"> <span class="nam">data</span> <span class="op">=</span> <span class="nam">load_alerts</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t255" href="#t255">255</a></span><span class="t"> <span class="nam">alerts</span> <span class="op">=</span> <span class="nam">data</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"alerts"</span><span class="op">,</span> <span class="op">[</span><span class="op">]</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t256" href="#t256">256</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t257" href="#t257">257</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">alerts</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t258" href="#t258">258</a></span><span class="t"> <span class="key">if</span> <span class="nam">args</span><span class="op">.</span><span class="nam">json</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t259" href="#t259">259</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="nam">json</span><span class="op">.</span><span class="nam">dumps</span><span class="op">(</span><span class="op">{</span><span class="str">"triggered"</span><span class="op">:</span> <span class="op">[</span><span class="op">]</span><span class="op">,</span> <span class="str">"watching"</span><span class="op">:</span> <span class="op">[</span><span class="op">]</span><span class="op">}</span><span class="op">)</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t260" href="#t260">260</a></span><span class="t"> <span class="key">else</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t261" href="#t261">261</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">"📭 No alerts to check"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t262" href="#t262">262</a></span><span class="t"> <span class="key">return</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t263" href="#t263">263</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t264" href="#t264">264</a></span><span class="t"> <span class="nam">now</span> <span class="op">=</span> <span class="nam">datetime</span><span class="op">.</span><span class="nam">now</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t265" href="#t265">265</a></span><span class="t"> <span class="nam">active_alerts</span> <span class="op">=</span> <span class="op">[</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t266" href="#t266">266</a></span><span class="t"> <span class="key">for</span> <span class="nam">alert</span> <span class="key">in</span> <span class="nam">alerts</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t267" href="#t267">267</a></span><span class="t"> <span class="nam">snooze_until</span> <span class="op">=</span> <span class="nam">alert</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"snooze_until"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t268" href="#t268">268</a></span><span class="t"> <span class="key">if</span> <span class="nam">snooze_until</span> <span class="key">and</span> <span class="nam">datetime</span><span class="op">.</span><span class="nam">fromisoformat</span><span class="op">(</span><span class="nam">snooze_until</span><span class="op">)</span> <span class="op">></span> <span class="nam">now</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t269" href="#t269">269</a></span><span class="t"> <span class="key">continue</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t270" href="#t270">270</a></span><span class="t"> <span class="nam">active_alerts</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="nam">alert</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t271" href="#t271">271</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t272" href="#t272">272</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">active_alerts</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t273" href="#t273">273</a></span><span class="t"> <span class="key">if</span> <span class="nam">args</span><span class="op">.</span><span class="nam">json</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t274" href="#t274">274</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="nam">json</span><span class="op">.</span><span class="nam">dumps</span><span class="op">(</span><span class="op">{</span><span class="str">"triggered"</span><span class="op">:</span> <span class="op">[</span><span class="op">]</span><span class="op">,</span> <span class="str">"watching"</span><span class="op">:</span> <span class="op">[</span><span class="op">]</span><span class="op">}</span><span class="op">)</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t275" href="#t275">275</a></span><span class="t"> <span class="key">else</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t276" href="#t276">276</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">"📭 All alerts snoozed"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t277" href="#t277">277</a></span><span class="t"> <span class="key">return</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t278" href="#t278">278</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t279" href="#t279">279</a></span><span class="t"> <span class="com"># Fetch prices for all active alerts</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t280" href="#t280">280</a></span><span class="t"> <span class="nam">tickers</span> <span class="op">=</span> <span class="op">[</span><span class="nam">a</span><span class="op">[</span><span class="str">"ticker"</span><span class="op">]</span> <span class="key">for</span> <span class="nam">a</span> <span class="key">in</span> <span class="nam">active_alerts</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t281" href="#t281">281</a></span><span class="t"> <span class="nam">quotes</span> <span class="op">=</span> <span class="nam">get_fetch_market_data</span><span class="op">(</span><span class="op">)</span><span class="op">(</span><span class="nam">tickers</span><span class="op">,</span> <span class="nam">timeout</span><span class="op">=</span><span class="num">30</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t282" href="#t282">282</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t283" href="#t283">283</a></span><span class="t"> <span class="nam">triggered</span> <span class="op">=</span> <span class="op">[</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t284" href="#t284">284</a></span><span class="t"> <span class="nam">watching</span> <span class="op">=</span> <span class="op">[</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t285" href="#t285">285</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t286" href="#t286">286</a></span><span class="t"> <span class="key">for</span> <span class="nam">alert</span> <span class="key">in</span> <span class="nam">active_alerts</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t287" href="#t287">287</a></span><span class="t"> <span class="nam">ticker</span> <span class="op">=</span> <span class="nam">alert</span><span class="op">[</span><span class="str">"ticker"</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t288" href="#t288">288</a></span><span class="t"> <span class="nam">target</span> <span class="op">=</span> <span class="nam">alert</span><span class="op">[</span><span class="str">"target_price"</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t289" href="#t289">289</a></span><span class="t"> <span class="nam">currency</span> <span class="op">=</span> <span class="nam">alert</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"currency"</span><span class="op">,</span> <span class="str">"USD"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t290" href="#t290">290</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t291" href="#t291">291</a></span><span class="t"> <span class="nam">quote</span> <span class="op">=</span> <span class="nam">quotes</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="nam">ticker</span><span class="op">,</span> <span class="op">{</span><span class="op">}</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t292" href="#t292">292</a></span><span class="t"> <span class="nam">price</span> <span class="op">=</span> <span class="nam">quote</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"price"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t293" href="#t293">293</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t294" href="#t294">294</a></span><span class="t"> <span class="key">if</span> <span class="nam">price</span> <span class="key">is</span> <span class="key">None</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t295" href="#t295">295</a></span><span class="t"> <span class="key">continue</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t296" href="#t296">296</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t297" href="#t297">297</a></span><span class="t"> <span class="com"># Divide-by-zero protection</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t298" href="#t298">298</a></span><span class="t"> <span class="key">if</span> <span class="nam">target</span> <span class="op">==</span> <span class="num">0</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t299" href="#t299">299</a></span><span class="t"> <span class="nam">pct_diff</span> <span class="op">=</span> <span class="num">0</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t300" href="#t300">300</a></span><span class="t"> <span class="key">else</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t301" href="#t301">301</a></span><span class="t"> <span class="nam">pct_diff</span> <span class="op">=</span> <span class="op">(</span><span class="op">(</span><span class="nam">price</span> <span class="op">-</span> <span class="nam">target</span><span class="op">)</span> <span class="op">/</span> <span class="nam">target</span><span class="op">)</span> <span class="op">*</span> <span class="num">100</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t302" href="#t302">302</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t303" href="#t303">303</a></span><span class="t"> <span class="nam">result</span> <span class="op">=</span> <span class="op">{</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t304" href="#t304">304</a></span><span class="t"> <span class="str">"ticker"</span><span class="op">:</span> <span class="nam">ticker</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t305" href="#t305">305</a></span><span class="t"> <span class="str">"target_price"</span><span class="op">:</span> <span class="nam">target</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t306" href="#t306">306</a></span><span class="t"> <span class="str">"current_price"</span><span class="op">:</span> <span class="nam">price</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t307" href="#t307">307</a></span><span class="t"> <span class="str">"currency"</span><span class="op">:</span> <span class="nam">currency</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t308" href="#t308">308</a></span><span class="t"> <span class="str">"pct_from_target"</span><span class="op">:</span> <span class="nam">round</span><span class="op">(</span><span class="nam">pct_diff</span><span class="op">,</span> <span class="num">2</span><span class="op">)</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t309" href="#t309">309</a></span><span class="t"> <span class="str">"note"</span><span class="op">:</span> <span class="nam">alert</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"note"</span><span class="op">,</span> <span class="str">""</span><span class="op">)</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t310" href="#t310">310</a></span><span class="t"> <span class="str">"set_by"</span><span class="op">:</span> <span class="nam">alert</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"set_by"</span><span class="op">,</span> <span class="str">""</span><span class="op">)</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t311" href="#t311">311</a></span><span class="t"> <span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t312" href="#t312">312</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t313" href="#t313">313</a></span><span class="t"> <span class="key">if</span> <span class="nam">price</span> <span class="op"><=</span> <span class="nam">target</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t314" href="#t314">314</a></span><span class="t"> <span class="nam">triggered</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="nam">result</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t315" href="#t315">315</a></span><span class="t"> <span class="com"># Update triggered count (only once per day to avoid inflation)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t316" href="#t316">316</a></span><span class="t"> <span class="nam">last_triggered</span> <span class="op">=</span> <span class="nam">alert</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"last_triggered"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t317" href="#t317">317</a></span><span class="t"> <span class="nam">today</span> <span class="op">=</span> <span class="nam">now</span><span class="op">.</span><span class="nam">strftime</span><span class="op">(</span><span class="str">"%Y-%m-%d"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t318" href="#t318">318</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">last_triggered</span> <span class="key">or</span> <span class="key">not</span> <span class="nam">last_triggered</span><span class="op">.</span><span class="nam">startswith</span><span class="op">(</span><span class="nam">today</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t319" href="#t319">319</a></span><span class="t"> <span class="nam">alert</span><span class="op">[</span><span class="str">"triggered_count"</span><span class="op">]</span> <span class="op">=</span> <span class="nam">alert</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"triggered_count"</span><span class="op">,</span> <span class="num">0</span><span class="op">)</span> <span class="op">+</span> <span class="num">1</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t320" href="#t320">320</a></span><span class="t"> <span class="nam">alert</span><span class="op">[</span><span class="str">"last_triggered"</span><span class="op">]</span> <span class="op">=</span> <span class="nam">now</span><span class="op">.</span><span class="nam">isoformat</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t321" href="#t321">321</a></span><span class="t"> <span class="key">else</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t322" href="#t322">322</a></span><span class="t"> <span class="nam">watching</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="nam">result</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t323" href="#t323">323</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t324" href="#t324">324</a></span><span class="t"> <span class="nam">save_alerts</span><span class="op">(</span><span class="nam">data</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t325" href="#t325">325</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t326" href="#t326">326</a></span><span class="t"> <span class="key">if</span> <span class="nam">args</span><span class="op">.</span><span class="nam">json</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t327" href="#t327">327</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="nam">json</span><span class="op">.</span><span class="nam">dumps</span><span class="op">(</span><span class="op">{</span><span class="str">"triggered"</span><span class="op">:</span> <span class="nam">triggered</span><span class="op">,</span> <span class="str">"watching"</span><span class="op">:</span> <span class="nam">watching</span><span class="op">}</span><span class="op">,</span> <span class="nam">indent</span><span class="op">=</span><span class="num">2</span><span class="op">)</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t328" href="#t328">328</a></span><span class="t"> <span class="key">return</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t329" href="#t329">329</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t330" href="#t330">330</a></span><span class="t"> <span class="com"># Translations</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t331" href="#t331">331</a></span><span class="t"> <span class="nam">lang</span> <span class="op">=</span> <span class="nam">getattr</span><span class="op">(</span><span class="nam">args</span><span class="op">,</span> <span class="str">'lang'</span><span class="op">,</span> <span class="str">'en'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t332" href="#t332">332</a></span><span class="t"> <span class="key">if</span> <span class="nam">lang</span> <span class="op">==</span> <span class="str">"de"</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t333" href="#t333">333</a></span><span class="t"> <span class="nam">labels</span> <span class="op">=</span> <span class="op">{</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t334" href="#t334">334</a></span><span class="t"> <span class="str">"title"</span><span class="op">:</span> <span class="str">"PREISWARNUNGEN"</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t335" href="#t335">335</a></span><span class="t"> <span class="str">"in_zone"</span><span class="op">:</span> <span class="str">"IN KAUFZONE"</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t336" href="#t336">336</a></span><span class="t"> <span class="str">"buy"</span><span class="op">:</span> <span class="str">"KAUFEN!"</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t337" href="#t337">337</a></span><span class="t"> <span class="str">"target"</span><span class="op">:</span> <span class="str">"Ziel"</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t338" href="#t338">338</a></span><span class="t"> <span class="str">"watching"</span><span class="op">:</span> <span class="str">"BEOBACHTUNG"</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t339" href="#t339">339</a></span><span class="t"> <span class="str">"to_target"</span><span class="op">:</span> <span class="str">"noch"</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t340" href="#t340">340</a></span><span class="t"> <span class="str">"no_data"</span><span class="op">:</span> <span class="str">"Keine Preisdaten für Alerts verfügbar"</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t341" href="#t341">341</a></span><span class="t"> <span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t342" href="#t342">342</a></span><span class="t"> <span class="key">else</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t343" href="#t343">343</a></span><span class="t"> <span class="nam">labels</span> <span class="op">=</span> <span class="op">{</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t344" href="#t344">344</a></span><span class="t"> <span class="str">"title"</span><span class="op">:</span> <span class="str">"PRICE ALERTS"</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t345" href="#t345">345</a></span><span class="t"> <span class="str">"in_zone"</span><span class="op">:</span> <span class="str">"IN BUY ZONE"</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t346" href="#t346">346</a></span><span class="t"> <span class="str">"buy"</span><span class="op">:</span> <span class="str">"BUY SIGNAL"</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t347" href="#t347">347</a></span><span class="t"> <span class="str">"target"</span><span class="op">:</span> <span class="str">"target"</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t348" href="#t348">348</a></span><span class="t"> <span class="str">"watching"</span><span class="op">:</span> <span class="str">"WATCHING"</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t349" href="#t349">349</a></span><span class="t"> <span class="str">"to_target"</span><span class="op">:</span> <span class="str">"to target"</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t350" href="#t350">350</a></span><span class="t"> <span class="str">"no_data"</span><span class="op">:</span> <span class="str">"No price data available for alerts"</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t351" href="#t351">351</a></span><span class="t"> <span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t352" href="#t352">352</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t353" href="#t353">353</a></span><span class="t"> <span class="com"># Date header</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t354" href="#t354">354</a></span><span class="t"> <span class="nam">date_str</span> <span class="op">=</span> <span class="nam">datetime</span><span class="op">.</span><span class="nam">now</span><span class="op">(</span><span class="op">)</span><span class="op">.</span><span class="nam">strftime</span><span class="op">(</span><span class="str">"%b %d, %Y"</span><span class="op">)</span> <span class="key">if</span> <span class="nam">lang</span> <span class="op">==</span> <span class="str">"en"</span> <span class="key">else</span> <span class="nam">datetime</span><span class="op">.</span><span class="nam">now</span><span class="op">(</span><span class="op">)</span><span class="op">.</span><span class="nam">strftime</span><span class="op">(</span><span class="str">"%d. %b %Y"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t355" href="#t355">355</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"📊 {labels['title']} — {date_str}\n"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t356" href="#t356">356</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t357" href="#t357">357</a></span><span class="t"> <span class="com"># Human-readable output</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t358" href="#t358">358</a></span><span class="t"> <span class="key">if</span> <span class="nam">triggered</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t359" href="#t359">359</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"🟢 {labels['in_zone']}:\n"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t360" href="#t360">360</a></span><span class="t"> <span class="key">for</span> <span class="nam">t</span> <span class="key">in</span> <span class="nam">triggered</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t361" href="#t361">361</a></span><span class="t"> <span class="nam">target_str</span> <span class="op">=</span> <span class="nam">format_price</span><span class="op">(</span><span class="nam">t</span><span class="op">[</span><span class="str">"target_price"</span><span class="op">]</span><span class="op">,</span> <span class="nam">t</span><span class="op">[</span><span class="str">"currency"</span><span class="op">]</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t362" href="#t362">362</a></span><span class="t"> <span class="nam">current_str</span> <span class="op">=</span> <span class="nam">format_price</span><span class="op">(</span><span class="nam">t</span><span class="op">[</span><span class="str">"current_price"</span><span class="op">]</span><span class="op">,</span> <span class="nam">t</span><span class="op">[</span><span class="str">"currency"</span><span class="op">]</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t363" href="#t363">363</a></span><span class="t"> <span class="nam">note</span> <span class="op">=</span> <span class="str">f'\n "{t["note"]}"'</span> <span class="key">if</span> <span class="nam">t</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"note"</span><span class="op">)</span> <span class="key">else</span> <span class="str">""</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t364" href="#t364">364</a></span><span class="t"> <span class="nam">user</span> <span class="op">=</span> <span class="str">f" — {t['set_by']}"</span> <span class="key">if</span> <span class="nam">t</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"set_by"</span><span class="op">)</span> <span class="key">else</span> <span class="str">""</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t365" href="#t365">365</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"• {t['ticker']}: {current_str} ({labels['target']}: {target_str}) ← {labels['buy']}{note}{user}"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t366" href="#t366">366</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t367" href="#t367">367</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t368" href="#t368">368</a></span><span class="t"> <span class="key">if</span> <span class="nam">watching</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t369" href="#t369">369</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"⏳ {labels['watching']}:\n"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t370" href="#t370">370</a></span><span class="t"> <span class="key">for</span> <span class="nam">w</span> <span class="key">in</span> <span class="nam">sorted</span><span class="op">(</span><span class="nam">watching</span><span class="op">,</span> <span class="nam">key</span><span class="op">=</span><span class="key">lambda</span> <span class="nam">x</span><span class="op">:</span> <span class="nam">x</span><span class="op">[</span><span class="str">"pct_from_target"</span><span class="op">]</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t371" href="#t371">371</a></span><span class="t"> <span class="nam">target_str</span> <span class="op">=</span> <span class="nam">format_price</span><span class="op">(</span><span class="nam">w</span><span class="op">[</span><span class="str">"target_price"</span><span class="op">]</span><span class="op">,</span> <span class="nam">w</span><span class="op">[</span><span class="str">"currency"</span><span class="op">]</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t372" href="#t372">372</a></span><span class="t"> <span class="nam">current_str</span> <span class="op">=</span> <span class="nam">format_price</span><span class="op">(</span><span class="nam">w</span><span class="op">[</span><span class="str">"current_price"</span><span class="op">]</span><span class="op">,</span> <span class="nam">w</span><span class="op">[</span><span class="str">"currency"</span><span class="op">]</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t373" href="#t373">373</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"• {w['ticker']}: {current_str} ({labels['target']}: {target_str}) — {labels['to_target']} {abs(w['pct_from_target']):.1f}%"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t374" href="#t374">374</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t375" href="#t375">375</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t376" href="#t376">376</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">triggered</span> <span class="key">and</span> <span class="key">not</span> <span class="nam">watching</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t377" href="#t377">377</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"📭 {labels['no_data']}"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t378" href="#t378">378</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t379" href="#t379">379</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t380" href="#t380">380</a></span><span class="t"><span class="key">def</span> <span class="nam">check_alerts</span><span class="op">(</span><span class="op">)</span> <span class="op">-></span> <span class="nam">dict</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t381" href="#t381">381</a></span><span class="t"> <span class="str">"""</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t382" href="#t382">382</a></span><span class="t"><span class="str"> Check alerts and return results for briefing integration.</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t383" href="#t383">383</a></span><span class="t"><span class="str"> Returns: {"triggered": [...], "watching": [...]}</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t384" href="#t384">384</a></span><span class="t"><span class="str"> """</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t385" href="#t385">385</a></span><span class="t"> <span class="nam">data</span> <span class="op">=</span> <span class="nam">load_alerts</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t386" href="#t386">386</a></span><span class="t"> <span class="nam">alerts</span> <span class="op">=</span> <span class="nam">data</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"alerts"</span><span class="op">,</span> <span class="op">[</span><span class="op">]</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t387" href="#t387">387</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t388" href="#t388">388</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">alerts</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t389" href="#t389">389</a></span><span class="t"> <span class="key">return</span> <span class="op">{</span><span class="str">"triggered"</span><span class="op">:</span> <span class="op">[</span><span class="op">]</span><span class="op">,</span> <span class="str">"watching"</span><span class="op">:</span> <span class="op">[</span><span class="op">]</span><span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t390" href="#t390">390</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t391" href="#t391">391</a></span><span class="t"> <span class="nam">now</span> <span class="op">=</span> <span class="nam">datetime</span><span class="op">.</span><span class="nam">now</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t392" href="#t392">392</a></span><span class="t"> <span class="nam">active_alerts</span> <span class="op">=</span> <span class="op">[</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t393" href="#t393">393</a></span><span class="t"> <span class="nam">a</span> <span class="key">for</span> <span class="nam">a</span> <span class="key">in</span> <span class="nam">alerts</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t394" href="#t394">394</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">a</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"snooze_until"</span><span class="op">)</span> <span class="key">or</span> <span class="nam">datetime</span><span class="op">.</span><span class="nam">fromisoformat</span><span class="op">(</span><span class="nam">a</span><span class="op">[</span><span class="str">"snooze_until"</span><span class="op">]</span><span class="op">)</span> <span class="op"><=</span> <span class="nam">now</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t395" href="#t395">395</a></span><span class="t"> <span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t396" href="#t396">396</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t397" href="#t397">397</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">active_alerts</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t398" href="#t398">398</a></span><span class="t"> <span class="key">return</span> <span class="op">{</span><span class="str">"triggered"</span><span class="op">:</span> <span class="op">[</span><span class="op">]</span><span class="op">,</span> <span class="str">"watching"</span><span class="op">:</span> <span class="op">[</span><span class="op">]</span><span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t399" href="#t399">399</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t400" href="#t400">400</a></span><span class="t"> <span class="nam">tickers</span> <span class="op">=</span> <span class="op">[</span><span class="nam">a</span><span class="op">[</span><span class="str">"ticker"</span><span class="op">]</span> <span class="key">for</span> <span class="nam">a</span> <span class="key">in</span> <span class="nam">active_alerts</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t401" href="#t401">401</a></span><span class="t"> <span class="nam">quotes</span> <span class="op">=</span> <span class="nam">get_fetch_market_data</span><span class="op">(</span><span class="op">)</span><span class="op">(</span><span class="nam">tickers</span><span class="op">,</span> <span class="nam">timeout</span><span class="op">=</span><span class="num">30</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t402" href="#t402">402</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t403" href="#t403">403</a></span><span class="t"> <span class="nam">triggered</span> <span class="op">=</span> <span class="op">[</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t404" href="#t404">404</a></span><span class="t"> <span class="nam">watching</span> <span class="op">=</span> <span class="op">[</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t405" href="#t405">405</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t406" href="#t406">406</a></span><span class="t"> <span class="key">for</span> <span class="nam">alert</span> <span class="key">in</span> <span class="nam">active_alerts</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t407" href="#t407">407</a></span><span class="t"> <span class="nam">ticker</span> <span class="op">=</span> <span class="nam">alert</span><span class="op">[</span><span class="str">"ticker"</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t408" href="#t408">408</a></span><span class="t"> <span class="nam">target</span> <span class="op">=</span> <span class="nam">alert</span><span class="op">[</span><span class="str">"target_price"</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t409" href="#t409">409</a></span><span class="t"> <span class="nam">currency</span> <span class="op">=</span> <span class="nam">alert</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"currency"</span><span class="op">,</span> <span class="str">"USD"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t410" href="#t410">410</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t411" href="#t411">411</a></span><span class="t"> <span class="nam">quote</span> <span class="op">=</span> <span class="nam">quotes</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="nam">ticker</span><span class="op">,</span> <span class="op">{</span><span class="op">}</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t412" href="#t412">412</a></span><span class="t"> <span class="nam">price</span> <span class="op">=</span> <span class="nam">quote</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"price"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t413" href="#t413">413</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t414" href="#t414">414</a></span><span class="t"> <span class="key">if</span> <span class="nam">price</span> <span class="key">is</span> <span class="key">None</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t415" href="#t415">415</a></span><span class="t"> <span class="key">continue</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t416" href="#t416">416</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t417" href="#t417">417</a></span><span class="t"> <span class="com"># Divide-by-zero protection</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t418" href="#t418">418</a></span><span class="t"> <span class="key">if</span> <span class="nam">target</span> <span class="op">==</span> <span class="num">0</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t419" href="#t419">419</a></span><span class="t"> <span class="nam">pct_diff</span> <span class="op">=</span> <span class="num">0</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t420" href="#t420">420</a></span><span class="t"> <span class="key">else</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t421" href="#t421">421</a></span><span class="t"> <span class="nam">pct_diff</span> <span class="op">=</span> <span class="op">(</span><span class="op">(</span><span class="nam">price</span> <span class="op">-</span> <span class="nam">target</span><span class="op">)</span> <span class="op">/</span> <span class="nam">target</span><span class="op">)</span> <span class="op">*</span> <span class="num">100</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t422" href="#t422">422</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t423" href="#t423">423</a></span><span class="t"> <span class="nam">result</span> <span class="op">=</span> <span class="op">{</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t424" href="#t424">424</a></span><span class="t"> <span class="str">"ticker"</span><span class="op">:</span> <span class="nam">ticker</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t425" href="#t425">425</a></span><span class="t"> <span class="str">"target_price"</span><span class="op">:</span> <span class="nam">target</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t426" href="#t426">426</a></span><span class="t"> <span class="str">"current_price"</span><span class="op">:</span> <span class="nam">price</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t427" href="#t427">427</a></span><span class="t"> <span class="str">"currency"</span><span class="op">:</span> <span class="nam">currency</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t428" href="#t428">428</a></span><span class="t"> <span class="str">"pct_from_target"</span><span class="op">:</span> <span class="nam">round</span><span class="op">(</span><span class="nam">pct_diff</span><span class="op">,</span> <span class="num">2</span><span class="op">)</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t429" href="#t429">429</a></span><span class="t"> <span class="str">"note"</span><span class="op">:</span> <span class="nam">alert</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"note"</span><span class="op">,</span> <span class="str">""</span><span class="op">)</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t430" href="#t430">430</a></span><span class="t"> <span class="str">"set_by"</span><span class="op">:</span> <span class="nam">alert</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"set_by"</span><span class="op">,</span> <span class="str">""</span><span class="op">)</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t431" href="#t431">431</a></span><span class="t"> <span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t432" href="#t432">432</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t433" href="#t433">433</a></span><span class="t"> <span class="key">if</span> <span class="nam">price</span> <span class="op"><=</span> <span class="nam">target</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t434" href="#t434">434</a></span><span class="t"> <span class="nam">triggered</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="nam">result</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t435" href="#t435">435</a></span><span class="t"> <span class="com"># Update triggered count (only once per day to avoid inflation)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t436" href="#t436">436</a></span><span class="t"> <span class="nam">last_triggered</span> <span class="op">=</span> <span class="nam">alert</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"last_triggered"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t437" href="#t437">437</a></span><span class="t"> <span class="nam">today</span> <span class="op">=</span> <span class="nam">now</span><span class="op">.</span><span class="nam">strftime</span><span class="op">(</span><span class="str">"%Y-%m-%d"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t438" href="#t438">438</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">last_triggered</span> <span class="key">or</span> <span class="key">not</span> <span class="nam">last_triggered</span><span class="op">.</span><span class="nam">startswith</span><span class="op">(</span><span class="nam">today</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t439" href="#t439">439</a></span><span class="t"> <span class="nam">alert</span><span class="op">[</span><span class="str">"triggered_count"</span><span class="op">]</span> <span class="op">=</span> <span class="nam">alert</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"triggered_count"</span><span class="op">,</span> <span class="num">0</span><span class="op">)</span> <span class="op">+</span> <span class="num">1</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t440" href="#t440">440</a></span><span class="t"> <span class="nam">alert</span><span class="op">[</span><span class="str">"last_triggered"</span><span class="op">]</span> <span class="op">=</span> <span class="nam">now</span><span class="op">.</span><span class="nam">isoformat</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t441" href="#t441">441</a></span><span class="t"> <span class="key">else</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t442" href="#t442">442</a></span><span class="t"> <span class="nam">watching</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="nam">result</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t443" href="#t443">443</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t444" href="#t444">444</a></span><span class="t"> <span class="nam">save_alerts</span><span class="op">(</span><span class="nam">data</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t445" href="#t445">445</a></span><span class="t"> <span class="key">return</span> <span class="op">{</span><span class="str">"triggered"</span><span class="op">:</span> <span class="nam">triggered</span><span class="op">,</span> <span class="str">"watching"</span><span class="op">:</span> <span class="nam">watching</span><span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t446" href="#t446">446</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t447" href="#t447">447</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t448" href="#t448">448</a></span><span class="t"><span class="key">def</span> <span class="nam">main</span><span class="op">(</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t449" href="#t449">449</a></span><span class="t"> <span class="nam">parser</span> <span class="op">=</span> <span class="nam">argparse</span><span class="op">.</span><span class="nam">ArgumentParser</span><span class="op">(</span><span class="nam">description</span><span class="op">=</span><span class="str">"Price target alerts"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t450" href="#t450">450</a></span><span class="t"> <span class="nam">subparsers</span> <span class="op">=</span> <span class="nam">parser</span><span class="op">.</span><span class="nam">add_subparsers</span><span class="op">(</span><span class="nam">dest</span><span class="op">=</span><span class="str">"command"</span><span class="op">,</span> <span class="nam">required</span><span class="op">=</span><span class="key">True</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t451" href="#t451">451</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t452" href="#t452">452</a></span><span class="t"> <span class="com"># list</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t453" href="#t453">453</a></span><span class="t"> <span class="nam">subparsers</span><span class="op">.</span><span class="nam">add_parser</span><span class="op">(</span><span class="str">"list"</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"List all alerts"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t454" href="#t454">454</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t455" href="#t455">455</a></span><span class="t"> <span class="com"># set</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t456" href="#t456">456</a></span><span class="t"> <span class="nam">set_parser</span> <span class="op">=</span> <span class="nam">subparsers</span><span class="op">.</span><span class="nam">add_parser</span><span class="op">(</span><span class="str">"set"</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"Set new alert"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t457" href="#t457">457</a></span><span class="t"> <span class="nam">set_parser</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">"ticker"</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"Stock ticker"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t458" href="#t458">458</a></span><span class="t"> <span class="nam">set_parser</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">"target"</span><span class="op">,</span> <span class="nam">type</span><span class="op">=</span><span class="nam">float</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"Target price"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t459" href="#t459">459</a></span><span class="t"> <span class="nam">set_parser</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">"--note"</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"Note/reason"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t460" href="#t460">460</a></span><span class="t"> <span class="nam">set_parser</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">"--user"</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"Who set the alert"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t461" href="#t461">461</a></span><span class="t"> <span class="nam">set_parser</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">"--currency"</span><span class="op">,</span> <span class="nam">default</span><span class="op">=</span><span class="str">"USD"</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"Currency (USD, EUR, JPY, SGD, MXN)"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t462" href="#t462">462</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t463" href="#t463">463</a></span><span class="t"> <span class="com"># delete</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t464" href="#t464">464</a></span><span class="t"> <span class="nam">del_parser</span> <span class="op">=</span> <span class="nam">subparsers</span><span class="op">.</span><span class="nam">add_parser</span><span class="op">(</span><span class="str">"delete"</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"Delete alert"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t465" href="#t465">465</a></span><span class="t"> <span class="nam">del_parser</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">"ticker"</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"Stock ticker"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t466" href="#t466">466</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t467" href="#t467">467</a></span><span class="t"> <span class="com"># snooze</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t468" href="#t468">468</a></span><span class="t"> <span class="nam">snooze_parser</span> <span class="op">=</span> <span class="nam">subparsers</span><span class="op">.</span><span class="nam">add_parser</span><span class="op">(</span><span class="str">"snooze"</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"Snooze alert"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t469" href="#t469">469</a></span><span class="t"> <span class="nam">snooze_parser</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">"ticker"</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"Stock ticker"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t470" href="#t470">470</a></span><span class="t"> <span class="nam">snooze_parser</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">"--days"</span><span class="op">,</span> <span class="nam">type</span><span class="op">=</span><span class="nam">int</span><span class="op">,</span> <span class="nam">default</span><span class="op">=</span><span class="num">7</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"Days to snooze"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t471" href="#t471">471</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t472" href="#t472">472</a></span><span class="t"> <span class="com"># update</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t473" href="#t473">473</a></span><span class="t"> <span class="nam">update_parser</span> <span class="op">=</span> <span class="nam">subparsers</span><span class="op">.</span><span class="nam">add_parser</span><span class="op">(</span><span class="str">"update"</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"Update alert target"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t474" href="#t474">474</a></span><span class="t"> <span class="nam">update_parser</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">"ticker"</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"Stock ticker"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t475" href="#t475">475</a></span><span class="t"> <span class="nam">update_parser</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">"target"</span><span class="op">,</span> <span class="nam">type</span><span class="op">=</span><span class="nam">float</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"New target price"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t476" href="#t476">476</a></span><span class="t"> <span class="nam">update_parser</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">"--note"</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"Update note"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t477" href="#t477">477</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t478" href="#t478">478</a></span><span class="t"> <span class="com"># check</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t479" href="#t479">479</a></span><span class="t"> <span class="nam">check_parser</span> <span class="op">=</span> <span class="nam">subparsers</span><span class="op">.</span><span class="nam">add_parser</span><span class="op">(</span><span class="str">"check"</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"Check alerts against prices"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t480" href="#t480">480</a></span><span class="t"> <span class="nam">check_parser</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">"--json"</span><span class="op">,</span> <span class="nam">action</span><span class="op">=</span><span class="str">"store_true"</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"JSON output"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t481" href="#t481">481</a></span><span class="t"> <span class="nam">check_parser</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">"--lang"</span><span class="op">,</span> <span class="nam">default</span><span class="op">=</span><span class="str">"en"</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"Output language (en, de)"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t482" href="#t482">482</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t483" href="#t483">483</a></span><span class="t"> <span class="nam">args</span> <span class="op">=</span> <span class="nam">parser</span><span class="op">.</span><span class="nam">parse_args</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t484" href="#t484">484</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t485" href="#t485">485</a></span><span class="t"> <span class="key">if</span> <span class="nam">args</span><span class="op">.</span><span class="nam">command</span> <span class="op">==</span> <span class="str">"list"</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t486" href="#t486">486</a></span><span class="t"> <span class="nam">cmd_list</span><span class="op">(</span><span class="nam">args</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t487" href="#t487">487</a></span><span class="t"> <span class="key">elif</span> <span class="nam">args</span><span class="op">.</span><span class="nam">command</span> <span class="op">==</span> <span class="str">"set"</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t488" href="#t488">488</a></span><span class="t"> <span class="nam">cmd_set</span><span class="op">(</span><span class="nam">args</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t489" href="#t489">489</a></span><span class="t"> <span class="key">elif</span> <span class="nam">args</span><span class="op">.</span><span class="nam">command</span> <span class="op">==</span> <span class="str">"delete"</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t490" href="#t490">490</a></span><span class="t"> <span class="nam">cmd_delete</span><span class="op">(</span><span class="nam">args</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t491" href="#t491">491</a></span><span class="t"> <span class="key">elif</span> <span class="nam">args</span><span class="op">.</span><span class="nam">command</span> <span class="op">==</span> <span class="str">"snooze"</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t492" href="#t492">492</a></span><span class="t"> <span class="nam">cmd_snooze</span><span class="op">(</span><span class="nam">args</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t493" href="#t493">493</a></span><span class="t"> <span class="key">elif</span> <span class="nam">args</span><span class="op">.</span><span class="nam">command</span> <span class="op">==</span> <span class="str">"update"</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t494" href="#t494">494</a></span><span class="t"> <span class="nam">cmd_update</span><span class="op">(</span><span class="nam">args</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t495" href="#t495">495</a></span><span class="t"> <span class="key">elif</span> <span class="nam">args</span><span class="op">.</span><span class="nam">command</span> <span class="op">==</span> <span class="str">"check"</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t496" href="#t496">496</a></span><span class="t"> <span class="nam">cmd_check</span><span class="op">(</span><span class="nam">args</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t497" href="#t497">497</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t498" href="#t498">498</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="exc show_exc"><span class="n"><a id="t499" href="#t499">499</a></span><span class="t"><span class="key">if</span> <span class="nam">__name__</span> <span class="op">==</span> <span class="str">"__main__"</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="exc show_exc"><span class="n"><a id="t500" href="#t500">500</a></span><span class="t"> <span class="nam">main</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
</main>
|
||||
<footer>
|
||||
<div class="content">
|
||||
<p>
|
||||
<a class="nav" href="index.html">« prev</a>
|
||||
<a class="nav" href="index.html">^ index</a>
|
||||
<a class="nav" href="z_de1a740d5dc98ffd_briefing_py.html">» next</a>
|
||||
|
||||
<a class="nav" href="https://coverage.readthedocs.io/en/7.13.2">coverage.py v7.13.2</a>,
|
||||
created at 2026-02-01 16:34 -0800
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
267
htmlcov/z_de1a740d5dc98ffd_briefing_py.html
generated
Normal file
267
htmlcov/z_de1a740d5dc98ffd_briefing_py.html
generated
Normal file
@@ -0,0 +1,267 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>Coverage for scripts/briefing.py: 56%</title>
|
||||
<link rel="icon" sizes="32x32" href="favicon_32_cb_c827f16f.png">
|
||||
<link rel="stylesheet" href="style_cb_9ff733b0.css" type="text/css">
|
||||
<script src="coverage_html_cb_dd2e7eb5.js" defer></script>
|
||||
</head>
|
||||
<body class="pyfile">
|
||||
<header>
|
||||
<div class="content">
|
||||
<h1>
|
||||
<span class="text">Coverage for </span><b>scripts / briefing.py</b>:
|
||||
<span class="pc_cov">56%</span>
|
||||
</h1>
|
||||
<aside id="help_panel_wrapper">
|
||||
<input id="help_panel_state" type="checkbox">
|
||||
<label for="help_panel_state">
|
||||
<img id="keyboard_icon" src="keybd_closed_cb_900cfef5.png" alt="Show/hide keyboard shortcuts">
|
||||
</label>
|
||||
<div id="help_panel">
|
||||
<p class="legend">Shortcuts on this page</p>
|
||||
<div class="keyhelp">
|
||||
<p>
|
||||
<kbd>r</kbd>
|
||||
<kbd>m</kbd>
|
||||
<kbd>x</kbd>
|
||||
toggle line displays
|
||||
</p>
|
||||
<p>
|
||||
<kbd>j</kbd>
|
||||
<kbd>k</kbd>
|
||||
next/prev highlighted chunk
|
||||
</p>
|
||||
<p>
|
||||
<kbd>0</kbd> (zero) top of page
|
||||
</p>
|
||||
<p>
|
||||
<kbd>1</kbd> (one) first highlighted chunk
|
||||
</p>
|
||||
<p>
|
||||
<kbd>[</kbd>
|
||||
<kbd>]</kbd>
|
||||
prev/next file
|
||||
</p>
|
||||
<p>
|
||||
<kbd>u</kbd> up to the index
|
||||
</p>
|
||||
<p>
|
||||
<kbd>?</kbd> show/hide this help
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
<h2>
|
||||
<span class="text">87 statements </span>
|
||||
<button type="button" class="run button_toggle_run" value="run" data-shortcut="r" title="Toggle lines run">49<span class="text"> run</span></button>
|
||||
<button type="button" class="mis show_mis button_toggle_mis" value="mis" data-shortcut="m" title="Toggle lines missing">38<span class="text"> missing</span></button>
|
||||
<button type="button" class="exc show_exc button_toggle_exc" value="exc" data-shortcut="x" title="Toggle lines excluded">2<span class="text"> excluded</span></button>
|
||||
</h2>
|
||||
<p class="text">
|
||||
<a id="prevFileLink" class="nav" href="z_de1a740d5dc98ffd_alerts_py.html">« prev</a>
|
||||
<a id="indexLink" class="nav" href="index.html">^ index</a>
|
||||
<a id="nextFileLink" class="nav" href="z_de1a740d5dc98ffd_earnings_py.html">» next</a>
|
||||
|
||||
<a class="nav" href="https://coverage.readthedocs.io/en/7.13.2">coverage.py v7.13.2</a>,
|
||||
created at 2026-02-01 16:34 -0800
|
||||
</p>
|
||||
<aside class="hidden">
|
||||
<button type="button" class="button_next_chunk" data-shortcut="j"></button>
|
||||
<button type="button" class="button_prev_chunk" data-shortcut="k"></button>
|
||||
<button type="button" class="button_top_of_page" data-shortcut="0"></button>
|
||||
<button type="button" class="button_first_chunk" data-shortcut="1"></button>
|
||||
<button type="button" class="button_prev_file" data-shortcut="["></button>
|
||||
<button type="button" class="button_next_file" data-shortcut="]"></button>
|
||||
<button type="button" class="button_to_index" data-shortcut="u"></button>
|
||||
<button type="button" class="button_show_hide_help" data-shortcut="?"></button>
|
||||
</aside>
|
||||
</div>
|
||||
</header>
|
||||
<main id="source">
|
||||
<p class="pln"><span class="n"><a id="t1" href="#t1">1</a></span><span class="t"><span class="com">#!/usr/bin/env python3</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t2" href="#t2">2</a></span><span class="t"><span class="str">"""</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t3" href="#t3">3</a></span><span class="t"><span class="str">Briefing Generator - Main entry point for market briefings.</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t4" href="#t4">4</a></span><span class="t"><span class="str">Generates and optionally sends to WhatsApp group.</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t5" href="#t5">5</a></span><span class="t"><span class="str">"""</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t6" href="#t6">6</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t7" href="#t7">7</a></span><span class="t"><span class="key">import</span> <span class="nam">argparse</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t8" href="#t8">8</a></span><span class="t"><span class="key">import</span> <span class="nam">json</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t9" href="#t9">9</a></span><span class="t"><span class="key">import</span> <span class="nam">os</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t10" href="#t10">10</a></span><span class="t"><span class="key">import</span> <span class="nam">subprocess</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t11" href="#t11">11</a></span><span class="t"><span class="key">import</span> <span class="nam">sys</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t12" href="#t12">12</a></span><span class="t"><span class="key">from</span> <span class="nam">datetime</span> <span class="key">import</span> <span class="nam">datetime</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t13" href="#t13">13</a></span><span class="t"><span class="key">from</span> <span class="nam">pathlib</span> <span class="key">import</span> <span class="nam">Path</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t14" href="#t14">14</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t15" href="#t15">15</a></span><span class="t"><span class="key">from</span> <span class="nam">utils</span> <span class="key">import</span> <span class="nam">ensure_venv</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t16" href="#t16">16</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t17" href="#t17">17</a></span><span class="t"><span class="nam">SCRIPT_DIR</span> <span class="op">=</span> <span class="nam">Path</span><span class="op">(</span><span class="nam">__file__</span><span class="op">)</span><span class="op">.</span><span class="nam">parent</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t18" href="#t18">18</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t19" href="#t19">19</a></span><span class="t"><span class="nam">ensure_venv</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t20" href="#t20">20</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t21" href="#t21">21</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t22" href="#t22">22</a></span><span class="t"><span class="key">def</span> <span class="nam">send_to_whatsapp</span><span class="op">(</span><span class="nam">message</span><span class="op">:</span> <span class="nam">str</span><span class="op">,</span> <span class="nam">group_name</span><span class="op">:</span> <span class="nam">str</span> <span class="op">|</span> <span class="key">None</span> <span class="op">=</span> <span class="key">None</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t23" href="#t23">23</a></span><span class="t"> <span class="str">"""Send message to WhatsApp group via openclaw message tool."""</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t24" href="#t24">24</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">group_name</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t25" href="#t25">25</a></span><span class="t"> <span class="nam">group_name</span> <span class="op">=</span> <span class="nam">os</span><span class="op">.</span><span class="nam">environ</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'FINANCE_NEWS_TARGET'</span><span class="op">,</span> <span class="str">''</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t26" href="#t26">26</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">group_name</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t27" href="#t27">27</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">"❌ No target specified. Set FINANCE_NEWS_TARGET env var or use --group"</span><span class="op">,</span> <span class="nam">file</span><span class="op">=</span><span class="nam">sys</span><span class="op">.</span><span class="nam">stderr</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t28" href="#t28">28</a></span><span class="t"> <span class="key">return</span> <span class="key">False</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t29" href="#t29">29</a></span><span class="t"> <span class="com"># Use openclaw message tool</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t30" href="#t30">30</a></span><span class="t"> <span class="key">try</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t31" href="#t31">31</a></span><span class="t"> <span class="nam">result</span> <span class="op">=</span> <span class="nam">subprocess</span><span class="op">.</span><span class="nam">run</span><span class="op">(</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t32" href="#t32">32</a></span><span class="t"> <span class="op">[</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t33" href="#t33">33</a></span><span class="t"> <span class="str">'openclaw'</span><span class="op">,</span> <span class="str">'message'</span><span class="op">,</span> <span class="str">'send'</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t34" href="#t34">34</a></span><span class="t"> <span class="str">'--channel'</span><span class="op">,</span> <span class="str">'whatsapp'</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t35" href="#t35">35</a></span><span class="t"> <span class="str">'--target'</span><span class="op">,</span> <span class="nam">group_name</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t36" href="#t36">36</a></span><span class="t"> <span class="str">'--message'</span><span class="op">,</span> <span class="nam">message</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t37" href="#t37">37</a></span><span class="t"> <span class="op">]</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t38" href="#t38">38</a></span><span class="t"> <span class="nam">capture_output</span><span class="op">=</span><span class="key">True</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t39" href="#t39">39</a></span><span class="t"> <span class="nam">text</span><span class="op">=</span><span class="key">True</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t40" href="#t40">40</a></span><span class="t"> <span class="nam">timeout</span><span class="op">=</span><span class="num">30</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t41" href="#t41">41</a></span><span class="t"> <span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t42" href="#t42">42</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t43" href="#t43">43</a></span><span class="t"> <span class="key">if</span> <span class="nam">result</span><span class="op">.</span><span class="nam">returncode</span> <span class="op">==</span> <span class="num">0</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t44" href="#t44">44</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"✅ Sent to WhatsApp group: {group_name}"</span><span class="op">,</span> <span class="nam">file</span><span class="op">=</span><span class="nam">sys</span><span class="op">.</span><span class="nam">stderr</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t45" href="#t45">45</a></span><span class="t"> <span class="key">return</span> <span class="key">True</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t46" href="#t46">46</a></span><span class="t"> <span class="key">else</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t47" href="#t47">47</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"⚠️ WhatsApp send failed: {result.stderr}"</span><span class="op">,</span> <span class="nam">file</span><span class="op">=</span><span class="nam">sys</span><span class="op">.</span><span class="nam">stderr</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t48" href="#t48">48</a></span><span class="t"> <span class="key">return</span> <span class="key">False</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t49" href="#t49">49</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t50" href="#t50">50</a></span><span class="t"> <span class="key">except</span> <span class="nam">Exception</span> <span class="key">as</span> <span class="nam">e</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t51" href="#t51">51</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"❌ WhatsApp error: {e}"</span><span class="op">,</span> <span class="nam">file</span><span class="op">=</span><span class="nam">sys</span><span class="op">.</span><span class="nam">stderr</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t52" href="#t52">52</a></span><span class="t"> <span class="key">return</span> <span class="key">False</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t53" href="#t53">53</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t54" href="#t54">54</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t55" href="#t55">55</a></span><span class="t"><span class="key">def</span> <span class="nam">generate_and_send</span><span class="op">(</span><span class="nam">args</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t56" href="#t56">56</a></span><span class="t"> <span class="str">"""Generate briefing and optionally send to WhatsApp."""</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t57" href="#t57">57</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t58" href="#t58">58</a></span><span class="t"> <span class="com"># Determine briefing type based on current time or args</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t59" href="#t59">59</a></span><span class="t"> <span class="key">if</span> <span class="nam">args</span><span class="op">.</span><span class="nam">time</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t60" href="#t60">60</a></span><span class="t"> <span class="nam">briefing_time</span> <span class="op">=</span> <span class="nam">args</span><span class="op">.</span><span class="nam">time</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t61" href="#t61">61</a></span><span class="t"> <span class="key">else</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t62" href="#t62">62</a></span><span class="t"> <span class="nam">hour</span> <span class="op">=</span> <span class="nam">datetime</span><span class="op">.</span><span class="nam">now</span><span class="op">(</span><span class="op">)</span><span class="op">.</span><span class="nam">hour</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t63" href="#t63">63</a></span><span class="t"> <span class="nam">briefing_time</span> <span class="op">=</span> <span class="str">'morning'</span> <span class="key">if</span> <span class="nam">hour</span> <span class="op"><</span> <span class="num">12</span> <span class="key">else</span> <span class="str">'evening'</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t64" href="#t64">64</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t65" href="#t65">65</a></span><span class="t"> <span class="com"># Generate the briefing</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t66" href="#t66">66</a></span><span class="t"> <span class="nam">cmd</span> <span class="op">=</span> <span class="op">[</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t67" href="#t67">67</a></span><span class="t"> <span class="nam">sys</span><span class="op">.</span><span class="nam">executable</span><span class="op">,</span> <span class="nam">SCRIPT_DIR</span> <span class="op">/</span> <span class="str">'summarize.py'</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t68" href="#t68">68</a></span><span class="t"> <span class="str">'--time'</span><span class="op">,</span> <span class="nam">briefing_time</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t69" href="#t69">69</a></span><span class="t"> <span class="str">'--style'</span><span class="op">,</span> <span class="nam">args</span><span class="op">.</span><span class="nam">style</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t70" href="#t70">70</a></span><span class="t"> <span class="str">'--lang'</span><span class="op">,</span> <span class="nam">args</span><span class="op">.</span><span class="nam">lang</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t71" href="#t71">71</a></span><span class="t"> <span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t72" href="#t72">72</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t73" href="#t73">73</a></span><span class="t"> <span class="key">if</span> <span class="nam">args</span><span class="op">.</span><span class="nam">deadline</span> <span class="key">is</span> <span class="key">not</span> <span class="key">None</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t74" href="#t74">74</a></span><span class="t"> <span class="nam">cmd</span><span class="op">.</span><span class="nam">extend</span><span class="op">(</span><span class="op">[</span><span class="str">'--deadline'</span><span class="op">,</span> <span class="nam">str</span><span class="op">(</span><span class="nam">args</span><span class="op">.</span><span class="nam">deadline</span><span class="op">)</span><span class="op">]</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t75" href="#t75">75</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t76" href="#t76">76</a></span><span class="t"> <span class="key">if</span> <span class="nam">args</span><span class="op">.</span><span class="nam">fast</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t77" href="#t77">77</a></span><span class="t"> <span class="nam">cmd</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="str">'--fast'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t78" href="#t78">78</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t79" href="#t79">79</a></span><span class="t"> <span class="key">if</span> <span class="nam">args</span><span class="op">.</span><span class="nam">llm</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t80" href="#t80">80</a></span><span class="t"> <span class="nam">cmd</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="str">'--llm'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t81" href="#t81">81</a></span><span class="t"> <span class="nam">cmd</span><span class="op">.</span><span class="nam">extend</span><span class="op">(</span><span class="op">[</span><span class="str">'--model'</span><span class="op">,</span> <span class="nam">args</span><span class="op">.</span><span class="nam">model</span><span class="op">]</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t82" href="#t82">82</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t83" href="#t83">83</a></span><span class="t"> <span class="key">if</span> <span class="nam">args</span><span class="op">.</span><span class="nam">debug</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t84" href="#t84">84</a></span><span class="t"> <span class="nam">cmd</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="str">'--debug'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t85" href="#t85">85</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t86" href="#t86">86</a></span><span class="t"> <span class="com"># Always use JSON for internal processing to handle splits</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t87" href="#t87">87</a></span><span class="t"> <span class="nam">cmd</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="str">'--json'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t88" href="#t88">88</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t89" href="#t89">89</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"📊 Generating {briefing_time} briefing..."</span><span class="op">,</span> <span class="nam">file</span><span class="op">=</span><span class="nam">sys</span><span class="op">.</span><span class="nam">stderr</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t90" href="#t90">90</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t91" href="#t91">91</a></span><span class="t"> <span class="nam">timeout</span> <span class="op">=</span> <span class="nam">args</span><span class="op">.</span><span class="nam">deadline</span> <span class="key">if</span> <span class="nam">args</span><span class="op">.</span><span class="nam">deadline</span> <span class="key">is</span> <span class="key">not</span> <span class="key">None</span> <span class="key">else</span> <span class="num">300</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t92" href="#t92">92</a></span><span class="t"> <span class="nam">timeout</span> <span class="op">=</span> <span class="nam">max</span><span class="op">(</span><span class="num">1</span><span class="op">,</span> <span class="nam">int</span><span class="op">(</span><span class="nam">timeout</span><span class="op">)</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t93" href="#t93">93</a></span><span class="t"> <span class="key">if</span> <span class="nam">args</span><span class="op">.</span><span class="nam">deadline</span> <span class="key">is</span> <span class="key">not</span> <span class="key">None</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t94" href="#t94">94</a></span><span class="t"> <span class="nam">timeout</span> <span class="op">=</span> <span class="nam">timeout</span> <span class="op">+</span> <span class="num">5</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t95" href="#t95">95</a></span><span class="t"> <span class="nam">result</span> <span class="op">=</span> <span class="nam">subprocess</span><span class="op">.</span><span class="nam">run</span><span class="op">(</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t96" href="#t96">96</a></span><span class="t"> <span class="nam">cmd</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t97" href="#t97">97</a></span><span class="t"> <span class="nam">capture_output</span><span class="op">=</span><span class="key">True</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t98" href="#t98">98</a></span><span class="t"> <span class="nam">text</span><span class="op">=</span><span class="key">True</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t99" href="#t99">99</a></span><span class="t"> <span class="nam">stdin</span><span class="op">=</span><span class="nam">subprocess</span><span class="op">.</span><span class="nam">DEVNULL</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t100" href="#t100">100</a></span><span class="t"> <span class="nam">timeout</span><span class="op">=</span><span class="nam">timeout</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t101" href="#t101">101</a></span><span class="t"> <span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t102" href="#t102">102</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t103" href="#t103">103</a></span><span class="t"> <span class="key">if</span> <span class="nam">result</span><span class="op">.</span><span class="nam">returncode</span> <span class="op">!=</span> <span class="num">0</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t104" href="#t104">104</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"❌ Briefing generation failed: {result.stderr}"</span><span class="op">,</span> <span class="nam">file</span><span class="op">=</span><span class="nam">sys</span><span class="op">.</span><span class="nam">stderr</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t105" href="#t105">105</a></span><span class="t"> <span class="nam">sys</span><span class="op">.</span><span class="nam">exit</span><span class="op">(</span><span class="num">1</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t106" href="#t106">106</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t107" href="#t107">107</a></span><span class="t"> <span class="key">try</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t108" href="#t108">108</a></span><span class="t"> <span class="nam">data</span> <span class="op">=</span> <span class="nam">json</span><span class="op">.</span><span class="nam">loads</span><span class="op">(</span><span class="nam">result</span><span class="op">.</span><span class="nam">stdout</span><span class="op">.</span><span class="nam">strip</span><span class="op">(</span><span class="op">)</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t109" href="#t109">109</a></span><span class="t"> <span class="key">except</span> <span class="nam">json</span><span class="op">.</span><span class="nam">JSONDecodeError</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t110" href="#t110">110</a></span><span class="t"> <span class="com"># Fallback if not JSON (shouldn't happen with --json)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t111" href="#t111">111</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"⚠️ Failed to parse briefing JSON"</span><span class="op">,</span> <span class="nam">file</span><span class="op">=</span><span class="nam">sys</span><span class="op">.</span><span class="nam">stderr</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t112" href="#t112">112</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="nam">result</span><span class="op">.</span><span class="nam">stdout</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t113" href="#t113">113</a></span><span class="t"> <span class="key">return</span> <span class="nam">result</span><span class="op">.</span><span class="nam">stdout</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t114" href="#t114">114</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t115" href="#t115">115</a></span><span class="t"> <span class="com"># Output handling</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t116" href="#t116">116</a></span><span class="t"> <span class="key">if</span> <span class="nam">args</span><span class="op">.</span><span class="nam">json</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t117" href="#t117">117</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="nam">json</span><span class="op">.</span><span class="nam">dumps</span><span class="op">(</span><span class="nam">data</span><span class="op">,</span> <span class="nam">indent</span><span class="op">=</span><span class="num">2</span><span class="op">)</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t118" href="#t118">118</a></span><span class="t"> <span class="key">else</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t119" href="#t119">119</a></span><span class="t"> <span class="com"># Print for humans</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t120" href="#t120">120</a></span><span class="t"> <span class="key">if</span> <span class="nam">data</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'macro_message'</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t121" href="#t121">121</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="nam">data</span><span class="op">[</span><span class="str">'macro_message'</span><span class="op">]</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t122" href="#t122">122</a></span><span class="t"> <span class="key">if</span> <span class="nam">data</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'portfolio_message'</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t123" href="#t123">123</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">"\n"</span> <span class="op">+</span> <span class="str">"="</span><span class="op">*</span><span class="num">20</span> <span class="op">+</span> <span class="str">"\n"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t124" href="#t124">124</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="nam">data</span><span class="op">[</span><span class="str">'portfolio_message'</span><span class="op">]</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t125" href="#t125">125</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t126" href="#t126">126</a></span><span class="t"> <span class="com"># Send to WhatsApp if requested</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t127" href="#t127">127</a></span><span class="t"> <span class="key">if</span> <span class="nam">args</span><span class="op">.</span><span class="nam">send</span> <span class="key">and</span> <span class="nam">args</span><span class="op">.</span><span class="nam">group</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t128" href="#t128">128</a></span><span class="t"> <span class="com"># Message 1: Macro</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t129" href="#t129">129</a></span><span class="t"> <span class="nam">macro_msg</span> <span class="op">=</span> <span class="nam">data</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'macro_message'</span><span class="op">)</span> <span class="key">or</span> <span class="nam">data</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'summary'</span><span class="op">,</span> <span class="str">''</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t130" href="#t130">130</a></span><span class="t"> <span class="key">if</span> <span class="nam">macro_msg</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t131" href="#t131">131</a></span><span class="t"> <span class="nam">send_to_whatsapp</span><span class="op">(</span><span class="nam">macro_msg</span><span class="op">,</span> <span class="nam">args</span><span class="op">.</span><span class="nam">group</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t132" href="#t132">132</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t133" href="#t133">133</a></span><span class="t"> <span class="com"># Message 2: Portfolio (if exists)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t134" href="#t134">134</a></span><span class="t"> <span class="nam">portfolio_msg</span> <span class="op">=</span> <span class="nam">data</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'portfolio_message'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t135" href="#t135">135</a></span><span class="t"> <span class="key">if</span> <span class="nam">portfolio_msg</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t136" href="#t136">136</a></span><span class="t"> <span class="nam">send_to_whatsapp</span><span class="op">(</span><span class="nam">portfolio_msg</span><span class="op">,</span> <span class="nam">args</span><span class="op">.</span><span class="nam">group</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t137" href="#t137">137</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t138" href="#t138">138</a></span><span class="t"> <span class="key">return</span> <span class="nam">data</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'macro_message'</span><span class="op">,</span> <span class="str">''</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t139" href="#t139">139</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t140" href="#t140">140</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t141" href="#t141">141</a></span><span class="t"><span class="key">def</span> <span class="nam">main</span><span class="op">(</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t142" href="#t142">142</a></span><span class="t"> <span class="nam">parser</span> <span class="op">=</span> <span class="nam">argparse</span><span class="op">.</span><span class="nam">ArgumentParser</span><span class="op">(</span><span class="nam">description</span><span class="op">=</span><span class="str">'Briefing Generator'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t143" href="#t143">143</a></span><span class="t"> <span class="nam">parser</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">'--time'</span><span class="op">,</span> <span class="nam">choices</span><span class="op">=</span><span class="op">[</span><span class="str">'morning'</span><span class="op">,</span> <span class="str">'evening'</span><span class="op">]</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t144" href="#t144">144</a></span><span class="t"> <span class="nam">help</span><span class="op">=</span><span class="str">'Briefing type (auto-detected if not specified)'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t145" href="#t145">145</a></span><span class="t"> <span class="nam">parser</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">'--style'</span><span class="op">,</span> <span class="nam">choices</span><span class="op">=</span><span class="op">[</span><span class="str">'briefing'</span><span class="op">,</span> <span class="str">'analysis'</span><span class="op">,</span> <span class="str">'headlines'</span><span class="op">]</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t146" href="#t146">146</a></span><span class="t"> <span class="nam">default</span><span class="op">=</span><span class="str">'briefing'</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">'Summary style'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t147" href="#t147">147</a></span><span class="t"> <span class="nam">parser</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">'--lang'</span><span class="op">,</span> <span class="nam">choices</span><span class="op">=</span><span class="op">[</span><span class="str">'en'</span><span class="op">,</span> <span class="str">'de'</span><span class="op">]</span><span class="op">,</span> <span class="nam">default</span><span class="op">=</span><span class="str">'en'</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t148" href="#t148">148</a></span><span class="t"> <span class="nam">help</span><span class="op">=</span><span class="str">'Output language'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t149" href="#t149">149</a></span><span class="t"> <span class="nam">parser</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">'--send'</span><span class="op">,</span> <span class="nam">action</span><span class="op">=</span><span class="str">'store_true'</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t150" href="#t150">150</a></span><span class="t"> <span class="nam">help</span><span class="op">=</span><span class="str">'Send to WhatsApp group'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t151" href="#t151">151</a></span><span class="t"> <span class="nam">parser</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">'--group'</span><span class="op">,</span> <span class="nam">default</span><span class="op">=</span><span class="nam">os</span><span class="op">.</span><span class="nam">environ</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'FINANCE_NEWS_TARGET'</span><span class="op">,</span> <span class="str">''</span><span class="op">)</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t152" href="#t152">152</a></span><span class="t"> <span class="nam">help</span><span class="op">=</span><span class="str">'WhatsApp group name or JID (default: FINANCE_NEWS_TARGET env var)'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t153" href="#t153">153</a></span><span class="t"> <span class="nam">parser</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">'--json'</span><span class="op">,</span> <span class="nam">action</span><span class="op">=</span><span class="str">'store_true'</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t154" href="#t154">154</a></span><span class="t"> <span class="nam">help</span><span class="op">=</span><span class="str">'Output as JSON'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t155" href="#t155">155</a></span><span class="t"> <span class="nam">parser</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">'--deadline'</span><span class="op">,</span> <span class="nam">type</span><span class="op">=</span><span class="nam">int</span><span class="op">,</span> <span class="nam">default</span><span class="op">=</span><span class="key">None</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t156" href="#t156">156</a></span><span class="t"> <span class="nam">help</span><span class="op">=</span><span class="str">'Overall deadline in seconds'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t157" href="#t157">157</a></span><span class="t"> <span class="nam">parser</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">'--llm'</span><span class="op">,</span> <span class="nam">action</span><span class="op">=</span><span class="str">'store_true'</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">'Use LLM summary'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t158" href="#t158">158</a></span><span class="t"> <span class="nam">parser</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">'--model'</span><span class="op">,</span> <span class="nam">choices</span><span class="op">=</span><span class="op">[</span><span class="str">'claude'</span><span class="op">,</span> <span class="str">'minimax'</span><span class="op">,</span> <span class="str">'gemini'</span><span class="op">]</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t159" href="#t159">159</a></span><span class="t"> <span class="nam">default</span><span class="op">=</span><span class="str">'claude'</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">'LLM model (only with --llm)'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t160" href="#t160">160</a></span><span class="t"> <span class="nam">parser</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">'--fast'</span><span class="op">,</span> <span class="nam">action</span><span class="op">=</span><span class="str">'store_true'</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t161" href="#t161">161</a></span><span class="t"> <span class="nam">help</span><span class="op">=</span><span class="str">'Use fast mode (shorter timeouts, fewer items)'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t162" href="#t162">162</a></span><span class="t"> <span class="nam">parser</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">'--debug'</span><span class="op">,</span> <span class="nam">action</span><span class="op">=</span><span class="str">'store_true'</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t163" href="#t163">163</a></span><span class="t"> <span class="nam">help</span><span class="op">=</span><span class="str">'Write debug log with sources'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t164" href="#t164">164</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t165" href="#t165">165</a></span><span class="t"> <span class="nam">args</span> <span class="op">=</span> <span class="nam">parser</span><span class="op">.</span><span class="nam">parse_args</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t166" href="#t166">166</a></span><span class="t"> <span class="nam">generate_and_send</span><span class="op">(</span><span class="nam">args</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t167" href="#t167">167</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t168" href="#t168">168</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="exc show_exc"><span class="n"><a id="t169" href="#t169">169</a></span><span class="t"><span class="key">if</span> <span class="nam">__name__</span> <span class="op">==</span> <span class="str">'__main__'</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="exc show_exc"><span class="n"><a id="t170" href="#t170">170</a></span><span class="t"> <span class="nam">main</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
</main>
|
||||
<footer>
|
||||
<div class="content">
|
||||
<p>
|
||||
<a class="nav" href="z_de1a740d5dc98ffd_alerts_py.html">« prev</a>
|
||||
<a class="nav" href="index.html">^ index</a>
|
||||
<a class="nav" href="z_de1a740d5dc98ffd_earnings_py.html">» next</a>
|
||||
|
||||
<a class="nav" href="https://coverage.readthedocs.io/en/7.13.2">coverage.py v7.13.2</a>,
|
||||
created at 2026-02-01 16:34 -0800
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
711
htmlcov/z_de1a740d5dc98ffd_earnings_py.html
generated
Normal file
711
htmlcov/z_de1a740d5dc98ffd_earnings_py.html
generated
Normal file
@@ -0,0 +1,711 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>Coverage for scripts/earnings.py: 45%</title>
|
||||
<link rel="icon" sizes="32x32" href="favicon_32_cb_c827f16f.png">
|
||||
<link rel="stylesheet" href="style_cb_9ff733b0.css" type="text/css">
|
||||
<script src="coverage_html_cb_dd2e7eb5.js" defer></script>
|
||||
</head>
|
||||
<body class="pyfile">
|
||||
<header>
|
||||
<div class="content">
|
||||
<h1>
|
||||
<span class="text">Coverage for </span><b>scripts / earnings.py</b>:
|
||||
<span class="pc_cov">45%</span>
|
||||
</h1>
|
||||
<aside id="help_panel_wrapper">
|
||||
<input id="help_panel_state" type="checkbox">
|
||||
<label for="help_panel_state">
|
||||
<img id="keyboard_icon" src="keybd_closed_cb_900cfef5.png" alt="Show/hide keyboard shortcuts">
|
||||
</label>
|
||||
<div id="help_panel">
|
||||
<p class="legend">Shortcuts on this page</p>
|
||||
<div class="keyhelp">
|
||||
<p>
|
||||
<kbd>r</kbd>
|
||||
<kbd>m</kbd>
|
||||
<kbd>x</kbd>
|
||||
toggle line displays
|
||||
</p>
|
||||
<p>
|
||||
<kbd>j</kbd>
|
||||
<kbd>k</kbd>
|
||||
next/prev highlighted chunk
|
||||
</p>
|
||||
<p>
|
||||
<kbd>0</kbd> (zero) top of page
|
||||
</p>
|
||||
<p>
|
||||
<kbd>1</kbd> (one) first highlighted chunk
|
||||
</p>
|
||||
<p>
|
||||
<kbd>[</kbd>
|
||||
<kbd>]</kbd>
|
||||
prev/next file
|
||||
</p>
|
||||
<p>
|
||||
<kbd>u</kbd> up to the index
|
||||
</p>
|
||||
<p>
|
||||
<kbd>?</kbd> show/hide this help
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
<h2>
|
||||
<span class="text">329 statements </span>
|
||||
<button type="button" class="run button_toggle_run" value="run" data-shortcut="r" title="Toggle lines run">148<span class="text"> run</span></button>
|
||||
<button type="button" class="mis show_mis button_toggle_mis" value="mis" data-shortcut="m" title="Toggle lines missing">181<span class="text"> missing</span></button>
|
||||
<button type="button" class="exc show_exc button_toggle_exc" value="exc" data-shortcut="x" title="Toggle lines excluded">2<span class="text"> excluded</span></button>
|
||||
</h2>
|
||||
<p class="text">
|
||||
<a id="prevFileLink" class="nav" href="z_de1a740d5dc98ffd_briefing_py.html">« prev</a>
|
||||
<a id="indexLink" class="nav" href="index.html">^ index</a>
|
||||
<a id="nextFileLink" class="nav" href="z_de1a740d5dc98ffd_fetch_news_py.html">» next</a>
|
||||
|
||||
<a class="nav" href="https://coverage.readthedocs.io/en/7.13.2">coverage.py v7.13.2</a>,
|
||||
created at 2026-02-01 16:34 -0800
|
||||
</p>
|
||||
<aside class="hidden">
|
||||
<button type="button" class="button_next_chunk" data-shortcut="j"></button>
|
||||
<button type="button" class="button_prev_chunk" data-shortcut="k"></button>
|
||||
<button type="button" class="button_top_of_page" data-shortcut="0"></button>
|
||||
<button type="button" class="button_first_chunk" data-shortcut="1"></button>
|
||||
<button type="button" class="button_prev_file" data-shortcut="["></button>
|
||||
<button type="button" class="button_next_file" data-shortcut="]"></button>
|
||||
<button type="button" class="button_to_index" data-shortcut="u"></button>
|
||||
<button type="button" class="button_show_hide_help" data-shortcut="?"></button>
|
||||
</aside>
|
||||
</div>
|
||||
</header>
|
||||
<main id="source">
|
||||
<p class="pln"><span class="n"><a id="t1" href="#t1">1</a></span><span class="t"><span class="com">#!/usr/bin/env python3</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t2" href="#t2">2</a></span><span class="t"><span class="str">"""</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t3" href="#t3">3</a></span><span class="t"><span class="str">Earnings Calendar - Track earnings dates for portfolio stocks.</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t4" href="#t4">4</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t5" href="#t5">5</a></span><span class="t"><span class="str">Features:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t6" href="#t6">6</a></span><span class="t"><span class="str">- Fetch earnings dates from FMP API</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t7" href="#t7">7</a></span><span class="t"><span class="str">- Show upcoming earnings in daily briefing</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t8" href="#t8">8</a></span><span class="t"><span class="str">- Alert 24h before earnings release</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t9" href="#t9">9</a></span><span class="t"><span class="str">- Cache results to avoid API spam</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t10" href="#t10">10</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t11" href="#t11">11</a></span><span class="t"><span class="str">Usage:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t12" href="#t12">12</a></span><span class="t"><span class="str"> earnings.py list # Show all upcoming earnings</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t13" href="#t13">13</a></span><span class="t"><span class="str"> earnings.py check # Check what's reporting today/this week</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t14" href="#t14">14</a></span><span class="t"><span class="str"> earnings.py refresh # Force refresh earnings data</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t15" href="#t15">15</a></span><span class="t"><span class="str">"""</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t16" href="#t16">16</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t17" href="#t17">17</a></span><span class="t"><span class="key">import</span> <span class="nam">argparse</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t18" href="#t18">18</a></span><span class="t"><span class="key">import</span> <span class="nam">csv</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t19" href="#t19">19</a></span><span class="t"><span class="key">import</span> <span class="nam">json</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t20" href="#t20">20</a></span><span class="t"><span class="key">import</span> <span class="nam">os</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t21" href="#t21">21</a></span><span class="t"><span class="key">import</span> <span class="nam">shutil</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t22" href="#t22">22</a></span><span class="t"><span class="key">import</span> <span class="nam">subprocess</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t23" href="#t23">23</a></span><span class="t"><span class="key">import</span> <span class="nam">sys</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t24" href="#t24">24</a></span><span class="t"><span class="key">from</span> <span class="nam">datetime</span> <span class="key">import</span> <span class="nam">datetime</span><span class="op">,</span> <span class="nam">timedelta</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t25" href="#t25">25</a></span><span class="t"><span class="key">from</span> <span class="nam">pathlib</span> <span class="key">import</span> <span class="nam">Path</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t26" href="#t26">26</a></span><span class="t"><span class="key">from</span> <span class="nam">urllib</span><span class="op">.</span><span class="nam">request</span> <span class="key">import</span> <span class="nam">urlopen</span><span class="op">,</span> <span class="nam">Request</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t27" href="#t27">27</a></span><span class="t"><span class="key">from</span> <span class="nam">urllib</span><span class="op">.</span><span class="nam">error</span> <span class="key">import</span> <span class="nam">URLError</span><span class="op">,</span> <span class="nam">HTTPError</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t28" href="#t28">28</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t29" href="#t29">29</a></span><span class="t"><span class="com"># Paths</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t30" href="#t30">30</a></span><span class="t"><span class="nam">SCRIPT_DIR</span> <span class="op">=</span> <span class="nam">Path</span><span class="op">(</span><span class="nam">__file__</span><span class="op">)</span><span class="op">.</span><span class="nam">parent</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t31" href="#t31">31</a></span><span class="t"><span class="nam">CONFIG_DIR</span> <span class="op">=</span> <span class="nam">SCRIPT_DIR</span><span class="op">.</span><span class="nam">parent</span> <span class="op">/</span> <span class="str">"config"</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t32" href="#t32">32</a></span><span class="t"><span class="nam">CACHE_DIR</span> <span class="op">=</span> <span class="nam">SCRIPT_DIR</span><span class="op">.</span><span class="nam">parent</span> <span class="op">/</span> <span class="str">"cache"</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t33" href="#t33">33</a></span><span class="t"><span class="nam">PORTFOLIO_FILE</span> <span class="op">=</span> <span class="nam">CONFIG_DIR</span> <span class="op">/</span> <span class="str">"portfolio.csv"</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t34" href="#t34">34</a></span><span class="t"><span class="nam">EARNINGS_CACHE</span> <span class="op">=</span> <span class="nam">CACHE_DIR</span> <span class="op">/</span> <span class="str">"earnings_calendar.json"</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t35" href="#t35">35</a></span><span class="t"><span class="nam">MANUAL_EARNINGS</span> <span class="op">=</span> <span class="nam">CONFIG_DIR</span> <span class="op">/</span> <span class="str">"manual_earnings.json"</span> <span class="com"># For JP/other stocks not in Finnhub</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t36" href="#t36">36</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t37" href="#t37">37</a></span><span class="t"><span class="com"># OpenBB binary path</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t38" href="#t38">38</a></span><span class="t"><span class="nam">OPENBB_BINARY</span> <span class="op">=</span> <span class="key">None</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t39" href="#t39">39</a></span><span class="t"><span class="key">try</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t40" href="#t40">40</a></span><span class="t"> <span class="nam">env_path</span> <span class="op">=</span> <span class="nam">os</span><span class="op">.</span><span class="nam">environ</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'OPENBB_QUOTE_BIN'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t41" href="#t41">41</a></span><span class="t"> <span class="key">if</span> <span class="nam">env_path</span> <span class="key">and</span> <span class="nam">os</span><span class="op">.</span><span class="nam">path</span><span class="op">.</span><span class="nam">isfile</span><span class="op">(</span><span class="nam">env_path</span><span class="op">)</span> <span class="key">and</span> <span class="nam">os</span><span class="op">.</span><span class="nam">access</span><span class="op">(</span><span class="nam">env_path</span><span class="op">,</span> <span class="nam">os</span><span class="op">.</span><span class="nam">X_OK</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t42" href="#t42">42</a></span><span class="t"> <span class="nam">OPENBB_BINARY</span> <span class="op">=</span> <span class="nam">env_path</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t43" href="#t43">43</a></span><span class="t"> <span class="key">else</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t44" href="#t44">44</a></span><span class="t"> <span class="nam">OPENBB_BINARY</span> <span class="op">=</span> <span class="nam">shutil</span><span class="op">.</span><span class="nam">which</span><span class="op">(</span><span class="str">'openbb-quote'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t45" href="#t45">45</a></span><span class="t"><span class="key">except</span> <span class="nam">Exception</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t46" href="#t46">46</a></span><span class="t"> <span class="key">pass</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t47" href="#t47">47</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t48" href="#t48">48</a></span><span class="t"><span class="com"># API Keys</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t49" href="#t49">49</a></span><span class="t"><span class="key">def</span> <span class="nam">get_fmp_key</span><span class="op">(</span><span class="op">)</span> <span class="op">-></span> <span class="nam">str</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t50" href="#t50">50</a></span><span class="t"> <span class="str">"""Get FMP API key from environment or .env file."""</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t51" href="#t51">51</a></span><span class="t"> <span class="nam">key</span> <span class="op">=</span> <span class="nam">os</span><span class="op">.</span><span class="nam">environ</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"FMP_API_KEY"</span><span class="op">,</span> <span class="str">""</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t52" href="#t52">52</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">key</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t53" href="#t53">53</a></span><span class="t"> <span class="nam">env_file</span> <span class="op">=</span> <span class="nam">Path</span><span class="op">.</span><span class="nam">home</span><span class="op">(</span><span class="op">)</span> <span class="op">/</span> <span class="str">".openclaw"</span> <span class="op">/</span> <span class="str">".env"</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t54" href="#t54">54</a></span><span class="t"> <span class="key">if</span> <span class="nam">env_file</span><span class="op">.</span><span class="nam">exists</span><span class="op">(</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t55" href="#t55">55</a></span><span class="t"> <span class="key">for</span> <span class="nam">line</span> <span class="key">in</span> <span class="nam">env_file</span><span class="op">.</span><span class="nam">read_text</span><span class="op">(</span><span class="op">)</span><span class="op">.</span><span class="nam">splitlines</span><span class="op">(</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t56" href="#t56">56</a></span><span class="t"> <span class="key">if</span> <span class="nam">line</span><span class="op">.</span><span class="nam">startswith</span><span class="op">(</span><span class="str">"FMP_API_KEY="</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t57" href="#t57">57</a></span><span class="t"> <span class="nam">key</span> <span class="op">=</span> <span class="nam">line</span><span class="op">.</span><span class="nam">split</span><span class="op">(</span><span class="str">"="</span><span class="op">,</span> <span class="num">1</span><span class="op">)</span><span class="op">[</span><span class="num">1</span><span class="op">]</span><span class="op">.</span><span class="nam">strip</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t58" href="#t58">58</a></span><span class="t"> <span class="key">break</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t59" href="#t59">59</a></span><span class="t"> <span class="key">return</span> <span class="nam">key</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t60" href="#t60">60</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t61" href="#t61">61</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t62" href="#t62">62</a></span><span class="t"><span class="key">def</span> <span class="nam">load_portfolio</span><span class="op">(</span><span class="op">)</span> <span class="op">-></span> <span class="nam">list</span><span class="op">[</span><span class="nam">dict</span><span class="op">]</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t63" href="#t63">63</a></span><span class="t"> <span class="str">"""Load portfolio from CSV."""</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t64" href="#t64">64</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">PORTFOLIO_FILE</span><span class="op">.</span><span class="nam">exists</span><span class="op">(</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t65" href="#t65">65</a></span><span class="t"> <span class="key">return</span> <span class="op">[</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t66" href="#t66">66</a></span><span class="t"> <span class="key">with</span> <span class="nam">open</span><span class="op">(</span><span class="nam">PORTFOLIO_FILE</span><span class="op">,</span> <span class="str">'r'</span><span class="op">)</span> <span class="key">as</span> <span class="nam">f</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t67" href="#t67">67</a></span><span class="t"> <span class="nam">reader</span> <span class="op">=</span> <span class="nam">csv</span><span class="op">.</span><span class="nam">DictReader</span><span class="op">(</span><span class="nam">f</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t68" href="#t68">68</a></span><span class="t"> <span class="key">return</span> <span class="nam">list</span><span class="op">(</span><span class="nam">reader</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t69" href="#t69">69</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t70" href="#t70">70</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t71" href="#t71">71</a></span><span class="t"><span class="key">def</span> <span class="nam">load_earnings_cache</span><span class="op">(</span><span class="op">)</span> <span class="op">-></span> <span class="nam">dict</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t72" href="#t72">72</a></span><span class="t"> <span class="str">"""Load cached earnings data."""</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t73" href="#t73">73</a></span><span class="t"> <span class="key">if</span> <span class="nam">EARNINGS_CACHE</span><span class="op">.</span><span class="nam">exists</span><span class="op">(</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t74" href="#t74">74</a></span><span class="t"> <span class="key">try</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t75" href="#t75">75</a></span><span class="t"> <span class="key">return</span> <span class="nam">json</span><span class="op">.</span><span class="nam">loads</span><span class="op">(</span><span class="nam">EARNINGS_CACHE</span><span class="op">.</span><span class="nam">read_text</span><span class="op">(</span><span class="op">)</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t76" href="#t76">76</a></span><span class="t"> <span class="key">except</span> <span class="nam">Exception</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t77" href="#t77">77</a></span><span class="t"> <span class="key">pass</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t78" href="#t78">78</a></span><span class="t"> <span class="key">return</span> <span class="op">{</span><span class="str">"last_updated"</span><span class="op">:</span> <span class="key">None</span><span class="op">,</span> <span class="str">"earnings"</span><span class="op">:</span> <span class="op">{</span><span class="op">}</span><span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t79" href="#t79">79</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t80" href="#t80">80</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t81" href="#t81">81</a></span><span class="t"><span class="key">def</span> <span class="nam">load_manual_earnings</span><span class="op">(</span><span class="op">)</span> <span class="op">-></span> <span class="nam">dict</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t82" href="#t82">82</a></span><span class="t"> <span class="str">"""</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t83" href="#t83">83</a></span><span class="t"><span class="str"> Load manually-entered earnings dates (for JP stocks not in Finnhub).</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t84" href="#t84">84</a></span><span class="t"><span class="str"> Format: {"6857.T": {"date": "2026-01-30", "time": "amc", "note": "Q3 FY2025"}, ...}</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t85" href="#t85">85</a></span><span class="t"><span class="str"> """</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t86" href="#t86">86</a></span><span class="t"> <span class="key">if</span> <span class="nam">MANUAL_EARNINGS</span><span class="op">.</span><span class="nam">exists</span><span class="op">(</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t87" href="#t87">87</a></span><span class="t"> <span class="key">try</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t88" href="#t88">88</a></span><span class="t"> <span class="nam">data</span> <span class="op">=</span> <span class="nam">json</span><span class="op">.</span><span class="nam">loads</span><span class="op">(</span><span class="nam">MANUAL_EARNINGS</span><span class="op">.</span><span class="nam">read_text</span><span class="op">(</span><span class="op">)</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t89" href="#t89">89</a></span><span class="t"> <span class="com"># Filter out metadata keys (starting with _)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t90" href="#t90">90</a></span><span class="t"> <span class="key">return</span> <span class="op">{</span><span class="nam">k</span><span class="op">:</span> <span class="nam">v</span> <span class="key">for</span> <span class="nam">k</span><span class="op">,</span> <span class="nam">v</span> <span class="key">in</span> <span class="nam">data</span><span class="op">.</span><span class="nam">items</span><span class="op">(</span><span class="op">)</span> <span class="key">if</span> <span class="key">not</span> <span class="nam">k</span><span class="op">.</span><span class="nam">startswith</span><span class="op">(</span><span class="str">"_"</span><span class="op">)</span> <span class="key">and</span> <span class="nam">isinstance</span><span class="op">(</span><span class="nam">v</span><span class="op">,</span> <span class="nam">dict</span><span class="op">)</span><span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t91" href="#t91">91</a></span><span class="t"> <span class="key">except</span> <span class="nam">Exception</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t92" href="#t92">92</a></span><span class="t"> <span class="key">pass</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t93" href="#t93">93</a></span><span class="t"> <span class="key">return</span> <span class="op">{</span><span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t94" href="#t94">94</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t95" href="#t95">95</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t96" href="#t96">96</a></span><span class="t"><span class="key">def</span> <span class="nam">save_earnings_cache</span><span class="op">(</span><span class="nam">data</span><span class="op">:</span> <span class="nam">dict</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t97" href="#t97">97</a></span><span class="t"> <span class="str">"""Save earnings data to cache."""</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t98" href="#t98">98</a></span><span class="t"> <span class="nam">CACHE_DIR</span><span class="op">.</span><span class="nam">mkdir</span><span class="op">(</span><span class="nam">exist_ok</span><span class="op">=</span><span class="key">True</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t99" href="#t99">99</a></span><span class="t"> <span class="nam">EARNINGS_CACHE</span><span class="op">.</span><span class="nam">write_text</span><span class="op">(</span><span class="nam">json</span><span class="op">.</span><span class="nam">dumps</span><span class="op">(</span><span class="nam">data</span><span class="op">,</span> <span class="nam">indent</span><span class="op">=</span><span class="num">2</span><span class="op">,</span> <span class="nam">default</span><span class="op">=</span><span class="nam">str</span><span class="op">)</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t100" href="#t100">100</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t101" href="#t101">101</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t102" href="#t102">102</a></span><span class="t"><span class="key">def</span> <span class="nam">get_finnhub_key</span><span class="op">(</span><span class="op">)</span> <span class="op">-></span> <span class="nam">str</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t103" href="#t103">103</a></span><span class="t"> <span class="str">"""Get Finnhub API key from environment or .env file."""</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t104" href="#t104">104</a></span><span class="t"> <span class="nam">key</span> <span class="op">=</span> <span class="nam">os</span><span class="op">.</span><span class="nam">environ</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"FINNHUB_API_KEY"</span><span class="op">,</span> <span class="str">""</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t105" href="#t105">105</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">key</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t106" href="#t106">106</a></span><span class="t"> <span class="nam">env_file</span> <span class="op">=</span> <span class="nam">Path</span><span class="op">.</span><span class="nam">home</span><span class="op">(</span><span class="op">)</span> <span class="op">/</span> <span class="str">".openclaw"</span> <span class="op">/</span> <span class="str">".env"</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t107" href="#t107">107</a></span><span class="t"> <span class="key">if</span> <span class="nam">env_file</span><span class="op">.</span><span class="nam">exists</span><span class="op">(</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t108" href="#t108">108</a></span><span class="t"> <span class="key">for</span> <span class="nam">line</span> <span class="key">in</span> <span class="nam">env_file</span><span class="op">.</span><span class="nam">read_text</span><span class="op">(</span><span class="op">)</span><span class="op">.</span><span class="nam">splitlines</span><span class="op">(</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t109" href="#t109">109</a></span><span class="t"> <span class="key">if</span> <span class="nam">line</span><span class="op">.</span><span class="nam">startswith</span><span class="op">(</span><span class="str">"FINNHUB_API_KEY="</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t110" href="#t110">110</a></span><span class="t"> <span class="nam">key</span> <span class="op">=</span> <span class="nam">line</span><span class="op">.</span><span class="nam">split</span><span class="op">(</span><span class="str">"="</span><span class="op">,</span> <span class="num">1</span><span class="op">)</span><span class="op">[</span><span class="num">1</span><span class="op">]</span><span class="op">.</span><span class="nam">strip</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t111" href="#t111">111</a></span><span class="t"> <span class="key">break</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t112" href="#t112">112</a></span><span class="t"> <span class="key">return</span> <span class="nam">key</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t113" href="#t113">113</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t114" href="#t114">114</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t115" href="#t115">115</a></span><span class="t"><span class="key">def</span> <span class="nam">fetch_all_earnings_finnhub</span><span class="op">(</span><span class="nam">days_ahead</span><span class="op">:</span> <span class="nam">int</span> <span class="op">=</span> <span class="num">60</span><span class="op">)</span> <span class="op">-></span> <span class="nam">dict</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t116" href="#t116">116</a></span><span class="t"> <span class="str">"""</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t117" href="#t117">117</a></span><span class="t"><span class="str"> Fetch all earnings for the next N days from Finnhub.</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t118" href="#t118">118</a></span><span class="t"><span class="str"> Returns dict keyed by symbol: {"AAPL": {...}, ...}</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t119" href="#t119">119</a></span><span class="t"><span class="str"> """</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t120" href="#t120">120</a></span><span class="t"> <span class="nam">finnhub_key</span> <span class="op">=</span> <span class="nam">get_finnhub_key</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t121" href="#t121">121</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">finnhub_key</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t122" href="#t122">122</a></span><span class="t"> <span class="key">return</span> <span class="op">{</span><span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t123" href="#t123">123</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t124" href="#t124">124</a></span><span class="t"> <span class="nam">from_date</span> <span class="op">=</span> <span class="nam">datetime</span><span class="op">.</span><span class="nam">now</span><span class="op">(</span><span class="op">)</span><span class="op">.</span><span class="nam">strftime</span><span class="op">(</span><span class="str">"%Y-%m-%d"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t125" href="#t125">125</a></span><span class="t"> <span class="nam">to_date</span> <span class="op">=</span> <span class="op">(</span><span class="nam">datetime</span><span class="op">.</span><span class="nam">now</span><span class="op">(</span><span class="op">)</span> <span class="op">+</span> <span class="nam">timedelta</span><span class="op">(</span><span class="nam">days</span><span class="op">=</span><span class="nam">days_ahead</span><span class="op">)</span><span class="op">)</span><span class="op">.</span><span class="nam">strftime</span><span class="op">(</span><span class="str">"%Y-%m-%d"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t126" href="#t126">126</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t127" href="#t127">127</a></span><span class="t"> <span class="nam">url</span> <span class="op">=</span> <span class="str">f"https://finnhub.io/api/v1/calendar/earnings?from={from_date}&to={to_date}&token={finnhub_key}"</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t128" href="#t128">128</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t129" href="#t129">129</a></span><span class="t"> <span class="key">try</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t130" href="#t130">130</a></span><span class="t"> <span class="nam">req</span> <span class="op">=</span> <span class="nam">Request</span><span class="op">(</span><span class="nam">url</span><span class="op">,</span> <span class="nam">headers</span><span class="op">=</span><span class="op">{</span><span class="str">"User-Agent"</span><span class="op">:</span> <span class="str">"finance-news/1.0"</span><span class="op">}</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t131" href="#t131">131</a></span><span class="t"> <span class="key">with</span> <span class="nam">urlopen</span><span class="op">(</span><span class="nam">req</span><span class="op">,</span> <span class="nam">timeout</span><span class="op">=</span><span class="num">30</span><span class="op">)</span> <span class="key">as</span> <span class="nam">resp</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t132" href="#t132">132</a></span><span class="t"> <span class="nam">data</span> <span class="op">=</span> <span class="nam">json</span><span class="op">.</span><span class="nam">loads</span><span class="op">(</span><span class="nam">resp</span><span class="op">.</span><span class="nam">read</span><span class="op">(</span><span class="op">)</span><span class="op">.</span><span class="nam">decode</span><span class="op">(</span><span class="str">"utf-8"</span><span class="op">)</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t133" href="#t133">133</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t134" href="#t134">134</a></span><span class="t"> <span class="nam">earnings_by_symbol</span> <span class="op">=</span> <span class="op">{</span><span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t135" href="#t135">135</a></span><span class="t"> <span class="key">for</span> <span class="nam">entry</span> <span class="key">in</span> <span class="nam">data</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"earningsCalendar"</span><span class="op">,</span> <span class="op">[</span><span class="op">]</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t136" href="#t136">136</a></span><span class="t"> <span class="nam">symbol</span> <span class="op">=</span> <span class="nam">entry</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"symbol"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t137" href="#t137">137</a></span><span class="t"> <span class="key">if</span> <span class="nam">symbol</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t138" href="#t138">138</a></span><span class="t"> <span class="nam">earnings_by_symbol</span><span class="op">[</span><span class="nam">symbol</span><span class="op">]</span> <span class="op">=</span> <span class="op">{</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t139" href="#t139">139</a></span><span class="t"> <span class="str">"date"</span><span class="op">:</span> <span class="nam">entry</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"date"</span><span class="op">)</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t140" href="#t140">140</a></span><span class="t"> <span class="str">"time"</span><span class="op">:</span> <span class="nam">entry</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"hour"</span><span class="op">,</span> <span class="str">""</span><span class="op">)</span><span class="op">,</span> <span class="com"># bmo/amc</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t141" href="#t141">141</a></span><span class="t"> <span class="str">"eps_estimate"</span><span class="op">:</span> <span class="nam">entry</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"epsEstimate"</span><span class="op">)</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t142" href="#t142">142</a></span><span class="t"> <span class="str">"revenue_estimate"</span><span class="op">:</span> <span class="nam">entry</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"revenueEstimate"</span><span class="op">)</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t143" href="#t143">143</a></span><span class="t"> <span class="str">"quarter"</span><span class="op">:</span> <span class="nam">entry</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"quarter"</span><span class="op">)</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t144" href="#t144">144</a></span><span class="t"> <span class="str">"year"</span><span class="op">:</span> <span class="nam">entry</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"year"</span><span class="op">)</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t145" href="#t145">145</a></span><span class="t"> <span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t146" href="#t146">146</a></span><span class="t"> <span class="key">return</span> <span class="nam">earnings_by_symbol</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t147" href="#t147">147</a></span><span class="t"> <span class="key">except</span> <span class="nam">Exception</span> <span class="key">as</span> <span class="nam">e</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t148" href="#t148">148</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"❌ Finnhub error: {e}"</span><span class="op">,</span> <span class="nam">file</span><span class="op">=</span><span class="nam">sys</span><span class="op">.</span><span class="nam">stderr</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t149" href="#t149">149</a></span><span class="t"> <span class="key">return</span> <span class="op">{</span><span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t150" href="#t150">150</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t151" href="#t151">151</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t152" href="#t152">152</a></span><span class="t"><span class="key">def</span> <span class="nam">normalize_ticker_for_lookup</span><span class="op">(</span><span class="nam">ticker</span><span class="op">:</span> <span class="nam">str</span><span class="op">)</span> <span class="op">-></span> <span class="nam">list</span><span class="op">[</span><span class="nam">str</span><span class="op">]</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t153" href="#t153">153</a></span><span class="t"> <span class="str">"""</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t154" href="#t154">154</a></span><span class="t"><span class="str"> Convert portfolio ticker to possible Finnhub symbols.</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t155" href="#t155">155</a></span><span class="t"><span class="str"> Returns list of possible formats to try.</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t156" href="#t156">156</a></span><span class="t"><span class="str"> """</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t157" href="#t157">157</a></span><span class="t"> <span class="nam">variants</span> <span class="op">=</span> <span class="op">[</span><span class="nam">ticker</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t158" href="#t158">158</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t159" href="#t159">159</a></span><span class="t"> <span class="com"># Japanese stocks: 6857.T -> try 6857</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t160" href="#t160">160</a></span><span class="t"> <span class="key">if</span> <span class="nam">ticker</span><span class="op">.</span><span class="nam">endswith</span><span class="op">(</span><span class="str">'.T'</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t161" href="#t161">161</a></span><span class="t"> <span class="nam">base</span> <span class="op">=</span> <span class="nam">ticker</span><span class="op">.</span><span class="nam">replace</span><span class="op">(</span><span class="str">'.T'</span><span class="op">,</span> <span class="str">''</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t162" href="#t162">162</a></span><span class="t"> <span class="nam">variants</span><span class="op">.</span><span class="nam">extend</span><span class="op">(</span><span class="op">[</span><span class="nam">base</span><span class="op">,</span> <span class="str">f"{base}.T"</span><span class="op">]</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t163" href="#t163">163</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t164" href="#t164">164</a></span><span class="t"> <span class="com"># Singapore stocks: D05.SI -> try D05</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t165" href="#t165">165</a></span><span class="t"> <span class="key">elif</span> <span class="nam">ticker</span><span class="op">.</span><span class="nam">endswith</span><span class="op">(</span><span class="str">'.SI'</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t166" href="#t166">166</a></span><span class="t"> <span class="nam">base</span> <span class="op">=</span> <span class="nam">ticker</span><span class="op">.</span><span class="nam">replace</span><span class="op">(</span><span class="str">'.SI'</span><span class="op">,</span> <span class="str">''</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t167" href="#t167">167</a></span><span class="t"> <span class="nam">variants</span><span class="op">.</span><span class="nam">extend</span><span class="op">(</span><span class="op">[</span><span class="nam">base</span><span class="op">,</span> <span class="str">f"{base}.SI"</span><span class="op">]</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t168" href="#t168">168</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t169" href="#t169">169</a></span><span class="t"> <span class="key">return</span> <span class="nam">variants</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t170" href="#t170">170</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t171" href="#t171">171</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t172" href="#t172">172</a></span><span class="t"><span class="key">def</span> <span class="nam">fetch_earnings_for_portfolio</span><span class="op">(</span><span class="nam">portfolio</span><span class="op">:</span> <span class="nam">list</span><span class="op">[</span><span class="nam">dict</span><span class="op">]</span><span class="op">)</span> <span class="op">-></span> <span class="nam">dict</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t173" href="#t173">173</a></span><span class="t"> <span class="str">"""</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t174" href="#t174">174</a></span><span class="t"><span class="str"> Fetch earnings dates for portfolio stocks using Finnhub bulk API.</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t175" href="#t175">175</a></span><span class="t"><span class="str"> More efficient than per-ticker calls.</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t176" href="#t176">176</a></span><span class="t"><span class="str"> """</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t177" href="#t177">177</a></span><span class="t"> <span class="com"># Get all earnings for next 60 days</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t178" href="#t178">178</a></span><span class="t"> <span class="nam">all_earnings</span> <span class="op">=</span> <span class="nam">fetch_all_earnings_finnhub</span><span class="op">(</span><span class="nam">days_ahead</span><span class="op">=</span><span class="num">60</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t179" href="#t179">179</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t180" href="#t180">180</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">all_earnings</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t181" href="#t181">181</a></span><span class="t"> <span class="key">return</span> <span class="op">{</span><span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t182" href="#t182">182</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t183" href="#t183">183</a></span><span class="t"> <span class="com"># Match portfolio tickers to earnings data</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t184" href="#t184">184</a></span><span class="t"> <span class="nam">results</span> <span class="op">=</span> <span class="op">{</span><span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t185" href="#t185">185</a></span><span class="t"> <span class="key">for</span> <span class="nam">stock</span> <span class="key">in</span> <span class="nam">portfolio</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t186" href="#t186">186</a></span><span class="t"> <span class="nam">ticker</span> <span class="op">=</span> <span class="nam">stock</span><span class="op">[</span><span class="str">"symbol"</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t187" href="#t187">187</a></span><span class="t"> <span class="nam">variants</span> <span class="op">=</span> <span class="nam">normalize_ticker_for_lookup</span><span class="op">(</span><span class="nam">ticker</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t188" href="#t188">188</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t189" href="#t189">189</a></span><span class="t"> <span class="key">for</span> <span class="nam">variant</span> <span class="key">in</span> <span class="nam">variants</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t190" href="#t190">190</a></span><span class="t"> <span class="key">if</span> <span class="nam">variant</span> <span class="key">in</span> <span class="nam">all_earnings</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t191" href="#t191">191</a></span><span class="t"> <span class="nam">results</span><span class="op">[</span><span class="nam">ticker</span><span class="op">]</span> <span class="op">=</span> <span class="nam">all_earnings</span><span class="op">[</span><span class="nam">variant</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t192" href="#t192">192</a></span><span class="t"> <span class="key">break</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t193" href="#t193">193</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t194" href="#t194">194</a></span><span class="t"> <span class="key">return</span> <span class="nam">results</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t195" href="#t195">195</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t196" href="#t196">196</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t197" href="#t197">197</a></span><span class="t"><span class="key">def</span> <span class="nam">refresh_earnings</span><span class="op">(</span><span class="nam">portfolio</span><span class="op">:</span> <span class="nam">list</span><span class="op">[</span><span class="nam">dict</span><span class="op">]</span><span class="op">,</span> <span class="nam">force</span><span class="op">:</span> <span class="nam">bool</span> <span class="op">=</span> <span class="key">False</span><span class="op">)</span> <span class="op">-></span> <span class="nam">dict</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t198" href="#t198">198</a></span><span class="t"> <span class="str">"""Refresh earnings data for all portfolio stocks."""</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t199" href="#t199">199</a></span><span class="t"> <span class="nam">finnhub_key</span> <span class="op">=</span> <span class="nam">get_finnhub_key</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t200" href="#t200">200</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">finnhub_key</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t201" href="#t201">201</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">"❌ FINNHUB_API_KEY not found"</span><span class="op">,</span> <span class="nam">file</span><span class="op">=</span><span class="nam">sys</span><span class="op">.</span><span class="nam">stderr</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t202" href="#t202">202</a></span><span class="t"> <span class="key">return</span> <span class="op">{</span><span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t203" href="#t203">203</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t204" href="#t204">204</a></span><span class="t"> <span class="nam">cache</span> <span class="op">=</span> <span class="nam">load_earnings_cache</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t205" href="#t205">205</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t206" href="#t206">206</a></span><span class="t"> <span class="com"># Check if cache is fresh (< 6 hours old)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t207" href="#t207">207</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">force</span> <span class="key">and</span> <span class="nam">cache</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"last_updated"</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t208" href="#t208">208</a></span><span class="t"> <span class="key">try</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t209" href="#t209">209</a></span><span class="t"> <span class="nam">last</span> <span class="op">=</span> <span class="nam">datetime</span><span class="op">.</span><span class="nam">fromisoformat</span><span class="op">(</span><span class="nam">cache</span><span class="op">[</span><span class="str">"last_updated"</span><span class="op">]</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t210" href="#t210">210</a></span><span class="t"> <span class="key">if</span> <span class="nam">datetime</span><span class="op">.</span><span class="nam">now</span><span class="op">(</span><span class="op">)</span> <span class="op">-</span> <span class="nam">last</span> <span class="op"><</span> <span class="nam">timedelta</span><span class="op">(</span><span class="nam">hours</span><span class="op">=</span><span class="num">6</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t211" href="#t211">211</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"📦 Using cached data (updated {last.strftime('%H:%M')})"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t212" href="#t212">212</a></span><span class="t"> <span class="key">return</span> <span class="nam">cache</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t213" href="#t213">213</a></span><span class="t"> <span class="key">except</span> <span class="nam">Exception</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t214" href="#t214">214</a></span><span class="t"> <span class="key">pass</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t215" href="#t215">215</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t216" href="#t216">216</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"🔄 Fetching earnings calendar from Finnhub..."</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t217" href="#t217">217</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t218" href="#t218">218</a></span><span class="t"> <span class="com"># Use bulk fetch - much more efficient</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t219" href="#t219">219</a></span><span class="t"> <span class="nam">earnings</span> <span class="op">=</span> <span class="nam">fetch_earnings_for_portfolio</span><span class="op">(</span><span class="nam">portfolio</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t220" href="#t220">220</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t221" href="#t221">221</a></span><span class="t"> <span class="com"># Merge manual earnings (for JP stocks not in Finnhub)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t222" href="#t222">222</a></span><span class="t"> <span class="nam">manual</span> <span class="op">=</span> <span class="nam">load_manual_earnings</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t223" href="#t223">223</a></span><span class="t"> <span class="key">if</span> <span class="nam">manual</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t224" href="#t224">224</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"📝 Merging {len(manual)} manual entries..."</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t225" href="#t225">225</a></span><span class="t"> <span class="key">for</span> <span class="nam">ticker</span><span class="op">,</span> <span class="nam">data</span> <span class="key">in</span> <span class="nam">manual</span><span class="op">.</span><span class="nam">items</span><span class="op">(</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t226" href="#t226">226</a></span><span class="t"> <span class="key">if</span> <span class="nam">ticker</span> <span class="key">not</span> <span class="key">in</span> <span class="nam">earnings</span><span class="op">:</span> <span class="com"># Manual data fills gaps</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t227" href="#t227">227</a></span><span class="t"> <span class="nam">earnings</span><span class="op">[</span><span class="nam">ticker</span><span class="op">]</span> <span class="op">=</span> <span class="nam">data</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t228" href="#t228">228</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t229" href="#t229">229</a></span><span class="t"> <span class="nam">found</span> <span class="op">=</span> <span class="nam">len</span><span class="op">(</span><span class="nam">earnings</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t230" href="#t230">230</a></span><span class="t"> <span class="nam">total</span> <span class="op">=</span> <span class="nam">len</span><span class="op">(</span><span class="nam">portfolio</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t231" href="#t231">231</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"✅ Found earnings data for {found}/{total} stocks"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t232" href="#t232">232</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t233" href="#t233">233</a></span><span class="t"> <span class="key">if</span> <span class="nam">earnings</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t234" href="#t234">234</a></span><span class="t"> <span class="key">for</span> <span class="nam">ticker</span><span class="op">,</span> <span class="nam">data</span> <span class="key">in</span> <span class="nam">sorted</span><span class="op">(</span><span class="nam">earnings</span><span class="op">.</span><span class="nam">items</span><span class="op">(</span><span class="op">)</span><span class="op">,</span> <span class="nam">key</span><span class="op">=</span><span class="key">lambda</span> <span class="nam">x</span><span class="op">:</span> <span class="nam">x</span><span class="op">[</span><span class="num">1</span><span class="op">]</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"date"</span><span class="op">,</span> <span class="str">""</span><span class="op">)</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t235" href="#t235">235</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f" • {ticker}: {data.get('date', '?')}"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t236" href="#t236">236</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t237" href="#t237">237</a></span><span class="t"> <span class="nam">cache</span> <span class="op">=</span> <span class="op">{</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t238" href="#t238">238</a></span><span class="t"> <span class="str">"last_updated"</span><span class="op">:</span> <span class="nam">datetime</span><span class="op">.</span><span class="nam">now</span><span class="op">(</span><span class="op">)</span><span class="op">.</span><span class="nam">isoformat</span><span class="op">(</span><span class="op">)</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t239" href="#t239">239</a></span><span class="t"> <span class="str">"earnings"</span><span class="op">:</span> <span class="nam">earnings</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t240" href="#t240">240</a></span><span class="t"> <span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t241" href="#t241">241</a></span><span class="t"> <span class="nam">save_earnings_cache</span><span class="op">(</span><span class="nam">cache</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t242" href="#t242">242</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t243" href="#t243">243</a></span><span class="t"> <span class="key">return</span> <span class="nam">cache</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t244" href="#t244">244</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t245" href="#t245">245</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t246" href="#t246">246</a></span><span class="t"><span class="key">def</span> <span class="nam">list_earnings</span><span class="op">(</span><span class="nam">args</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t247" href="#t247">247</a></span><span class="t"> <span class="str">"""List all upcoming earnings for portfolio."""</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t248" href="#t248">248</a></span><span class="t"> <span class="nam">portfolio</span> <span class="op">=</span> <span class="nam">load_portfolio</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t249" href="#t249">249</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">portfolio</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t250" href="#t250">250</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">"📂 Portfolio empty"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t251" href="#t251">251</a></span><span class="t"> <span class="key">return</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t252" href="#t252">252</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t253" href="#t253">253</a></span><span class="t"> <span class="nam">cache</span> <span class="op">=</span> <span class="nam">refresh_earnings</span><span class="op">(</span><span class="nam">portfolio</span><span class="op">,</span> <span class="nam">force</span><span class="op">=</span><span class="nam">args</span><span class="op">.</span><span class="nam">refresh</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t254" href="#t254">254</a></span><span class="t"> <span class="nam">earnings</span> <span class="op">=</span> <span class="nam">cache</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"earnings"</span><span class="op">,</span> <span class="op">{</span><span class="op">}</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t255" href="#t255">255</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t256" href="#t256">256</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">earnings</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t257" href="#t257">257</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">"\n❌ No earnings dates found"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t258" href="#t258">258</a></span><span class="t"> <span class="key">return</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t259" href="#t259">259</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t260" href="#t260">260</a></span><span class="t"> <span class="com"># Sort by date</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t261" href="#t261">261</a></span><span class="t"> <span class="nam">sorted_earnings</span> <span class="op">=</span> <span class="nam">sorted</span><span class="op">(</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t262" href="#t262">262</a></span><span class="t"> <span class="op">[</span><span class="op">(</span><span class="nam">ticker</span><span class="op">,</span> <span class="nam">data</span><span class="op">)</span> <span class="key">for</span> <span class="nam">ticker</span><span class="op">,</span> <span class="nam">data</span> <span class="key">in</span> <span class="nam">earnings</span><span class="op">.</span><span class="nam">items</span><span class="op">(</span><span class="op">)</span> <span class="key">if</span> <span class="nam">data</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"date"</span><span class="op">)</span><span class="op">]</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t263" href="#t263">263</a></span><span class="t"> <span class="nam">key</span><span class="op">=</span><span class="key">lambda</span> <span class="nam">x</span><span class="op">:</span> <span class="nam">x</span><span class="op">[</span><span class="num">1</span><span class="op">]</span><span class="op">[</span><span class="str">"date"</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t264" href="#t264">264</a></span><span class="t"> <span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t265" href="#t265">265</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t266" href="#t266">266</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"\n📅 Upcoming Earnings ({len(sorted_earnings)} stocks)\n"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t267" href="#t267">267</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t268" href="#t268">268</a></span><span class="t"> <span class="nam">today</span> <span class="op">=</span> <span class="nam">datetime</span><span class="op">.</span><span class="nam">now</span><span class="op">(</span><span class="op">)</span><span class="op">.</span><span class="nam">date</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t269" href="#t269">269</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t270" href="#t270">270</a></span><span class="t"> <span class="key">for</span> <span class="nam">ticker</span><span class="op">,</span> <span class="nam">data</span> <span class="key">in</span> <span class="nam">sorted_earnings</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t271" href="#t271">271</a></span><span class="t"> <span class="nam">date_str</span> <span class="op">=</span> <span class="nam">data</span><span class="op">[</span><span class="str">"date"</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t272" href="#t272">272</a></span><span class="t"> <span class="key">try</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t273" href="#t273">273</a></span><span class="t"> <span class="nam">ed</span> <span class="op">=</span> <span class="nam">datetime</span><span class="op">.</span><span class="nam">strptime</span><span class="op">(</span><span class="nam">date_str</span><span class="op">,</span> <span class="str">"%Y-%m-%d"</span><span class="op">)</span><span class="op">.</span><span class="nam">date</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t274" href="#t274">274</a></span><span class="t"> <span class="nam">days_until</span> <span class="op">=</span> <span class="op">(</span><span class="nam">ed</span> <span class="op">-</span> <span class="nam">today</span><span class="op">)</span><span class="op">.</span><span class="nam">days</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t275" href="#t275">275</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t276" href="#t276">276</a></span><span class="t"> <span class="com"># Emoji based on timing</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t277" href="#t277">277</a></span><span class="t"> <span class="key">if</span> <span class="nam">days_until</span> <span class="op"><</span> <span class="num">0</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t278" href="#t278">278</a></span><span class="t"> <span class="nam">emoji</span> <span class="op">=</span> <span class="str">"✅"</span> <span class="com"># Past</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t279" href="#t279">279</a></span><span class="t"> <span class="nam">timing</span> <span class="op">=</span> <span class="str">f"{-days_until}d ago"</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t280" href="#t280">280</a></span><span class="t"> <span class="key">elif</span> <span class="nam">days_until</span> <span class="op">==</span> <span class="num">0</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t281" href="#t281">281</a></span><span class="t"> <span class="nam">emoji</span> <span class="op">=</span> <span class="str">"🔴"</span> <span class="com"># Today!</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t282" href="#t282">282</a></span><span class="t"> <span class="nam">timing</span> <span class="op">=</span> <span class="str">"TODAY"</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t283" href="#t283">283</a></span><span class="t"> <span class="key">elif</span> <span class="nam">days_until</span> <span class="op">==</span> <span class="num">1</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t284" href="#t284">284</a></span><span class="t"> <span class="nam">emoji</span> <span class="op">=</span> <span class="str">"🟡"</span> <span class="com"># Tomorrow</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t285" href="#t285">285</a></span><span class="t"> <span class="nam">timing</span> <span class="op">=</span> <span class="str">"TOMORROW"</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t286" href="#t286">286</a></span><span class="t"> <span class="key">elif</span> <span class="nam">days_until</span> <span class="op"><=</span> <span class="num">7</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t287" href="#t287">287</a></span><span class="t"> <span class="nam">emoji</span> <span class="op">=</span> <span class="str">"🟠"</span> <span class="com"># This week</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t288" href="#t288">288</a></span><span class="t"> <span class="nam">timing</span> <span class="op">=</span> <span class="str">f"in {days_until}d"</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t289" href="#t289">289</a></span><span class="t"> <span class="key">else</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t290" href="#t290">290</a></span><span class="t"> <span class="nam">emoji</span> <span class="op">=</span> <span class="str">"⚪"</span> <span class="com"># Later</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t291" href="#t291">291</a></span><span class="t"> <span class="nam">timing</span> <span class="op">=</span> <span class="str">f"in {days_until}d"</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t292" href="#t292">292</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t293" href="#t293">293</a></span><span class="t"> <span class="com"># Time of day</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t294" href="#t294">294</a></span><span class="t"> <span class="nam">time_str</span> <span class="op">=</span> <span class="str">""</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t295" href="#t295">295</a></span><span class="t"> <span class="key">if</span> <span class="nam">data</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"time"</span><span class="op">)</span> <span class="op">==</span> <span class="str">"bmo"</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t296" href="#t296">296</a></span><span class="t"> <span class="nam">time_str</span> <span class="op">=</span> <span class="str">" (pre-market)"</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t297" href="#t297">297</a></span><span class="t"> <span class="key">elif</span> <span class="nam">data</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"time"</span><span class="op">)</span> <span class="op">==</span> <span class="str">"amc"</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t298" href="#t298">298</a></span><span class="t"> <span class="nam">time_str</span> <span class="op">=</span> <span class="str">" (after-close)"</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t299" href="#t299">299</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t300" href="#t300">300</a></span><span class="t"> <span class="com"># EPS estimate</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t301" href="#t301">301</a></span><span class="t"> <span class="nam">eps_str</span> <span class="op">=</span> <span class="str">""</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t302" href="#t302">302</a></span><span class="t"> <span class="key">if</span> <span class="nam">data</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"eps_estimate"</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t303" href="#t303">303</a></span><span class="t"> <span class="nam">eps_str</span> <span class="op">=</span> <span class="str">f" | Est: ${data['eps_estimate']:.2f}"</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t304" href="#t304">304</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t305" href="#t305">305</a></span><span class="t"> <span class="com"># Stock name from portfolio</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t306" href="#t306">306</a></span><span class="t"> <span class="nam">stock_name</span> <span class="op">=</span> <span class="nam">next</span><span class="op">(</span><span class="op">(</span><span class="nam">s</span><span class="op">[</span><span class="str">"name"</span><span class="op">]</span> <span class="key">for</span> <span class="nam">s</span> <span class="key">in</span> <span class="nam">portfolio</span> <span class="key">if</span> <span class="nam">s</span><span class="op">[</span><span class="str">"symbol"</span><span class="op">]</span> <span class="op">==</span> <span class="nam">ticker</span><span class="op">)</span><span class="op">,</span> <span class="nam">ticker</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t307" href="#t307">307</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t308" href="#t308">308</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"{emoji} {date_str} ({timing}): **{ticker}** — {stock_name}{time_str}{eps_str}"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t309" href="#t309">309</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t310" href="#t310">310</a></span><span class="t"> <span class="key">except</span> <span class="nam">ValueError</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t311" href="#t311">311</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"⚪ {date_str}: {ticker}"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t312" href="#t312">312</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t313" href="#t313">313</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t314" href="#t314">314</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t315" href="#t315">315</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t316" href="#t316">316</a></span><span class="t"><span class="key">def</span> <span class="nam">check_earnings</span><span class="op">(</span><span class="nam">args</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t317" href="#t317">317</a></span><span class="t"> <span class="str">"""Check earnings for today and this week (briefing format)."""</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t318" href="#t318">318</a></span><span class="t"> <span class="nam">portfolio</span> <span class="op">=</span> <span class="nam">load_portfolio</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t319" href="#t319">319</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">portfolio</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t320" href="#t320">320</a></span><span class="t"> <span class="key">return</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t321" href="#t321">321</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t322" href="#t322">322</a></span><span class="t"> <span class="nam">cache</span> <span class="op">=</span> <span class="nam">load_earnings_cache</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t323" href="#t323">323</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t324" href="#t324">324</a></span><span class="t"> <span class="com"># Auto-refresh if cache is stale</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t325" href="#t325">325</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">cache</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"last_updated"</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t326" href="#t326">326</a></span><span class="t"> <span class="nam">cache</span> <span class="op">=</span> <span class="nam">refresh_earnings</span><span class="op">(</span><span class="nam">portfolio</span><span class="op">,</span> <span class="nam">force</span><span class="op">=</span><span class="key">False</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t327" href="#t327">327</a></span><span class="t"> <span class="key">else</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t328" href="#t328">328</a></span><span class="t"> <span class="key">try</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t329" href="#t329">329</a></span><span class="t"> <span class="nam">last</span> <span class="op">=</span> <span class="nam">datetime</span><span class="op">.</span><span class="nam">fromisoformat</span><span class="op">(</span><span class="nam">cache</span><span class="op">[</span><span class="str">"last_updated"</span><span class="op">]</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t330" href="#t330">330</a></span><span class="t"> <span class="key">if</span> <span class="nam">datetime</span><span class="op">.</span><span class="nam">now</span><span class="op">(</span><span class="op">)</span> <span class="op">-</span> <span class="nam">last</span> <span class="op">></span> <span class="nam">timedelta</span><span class="op">(</span><span class="nam">hours</span><span class="op">=</span><span class="num">12</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t331" href="#t331">331</a></span><span class="t"> <span class="nam">cache</span> <span class="op">=</span> <span class="nam">refresh_earnings</span><span class="op">(</span><span class="nam">portfolio</span><span class="op">,</span> <span class="nam">force</span><span class="op">=</span><span class="key">False</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t332" href="#t332">332</a></span><span class="t"> <span class="key">except</span> <span class="nam">Exception</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t333" href="#t333">333</a></span><span class="t"> <span class="nam">cache</span> <span class="op">=</span> <span class="nam">refresh_earnings</span><span class="op">(</span><span class="nam">portfolio</span><span class="op">,</span> <span class="nam">force</span><span class="op">=</span><span class="key">False</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t334" href="#t334">334</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t335" href="#t335">335</a></span><span class="t"> <span class="nam">earnings</span> <span class="op">=</span> <span class="nam">cache</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"earnings"</span><span class="op">,</span> <span class="op">{</span><span class="op">}</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t336" href="#t336">336</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">earnings</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t337" href="#t337">337</a></span><span class="t"> <span class="key">return</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t338" href="#t338">338</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t339" href="#t339">339</a></span><span class="t"> <span class="nam">today</span> <span class="op">=</span> <span class="nam">datetime</span><span class="op">.</span><span class="nam">now</span><span class="op">(</span><span class="op">)</span><span class="op">.</span><span class="nam">date</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t340" href="#t340">340</a></span><span class="t"> <span class="nam">week_only</span> <span class="op">=</span> <span class="nam">getattr</span><span class="op">(</span><span class="nam">args</span><span class="op">,</span> <span class="str">'week'</span><span class="op">,</span> <span class="key">False</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t341" href="#t341">341</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t342" href="#t342">342</a></span><span class="t"> <span class="com"># For weekly mode (Sunday cron), show Mon-Fri of upcoming week</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t343" href="#t343">343</a></span><span class="t"> <span class="com"># Calculation: weekday() returns 0=Mon, 6=Sun. (7 - weekday) % 7 gives days until next Monday.</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t344" href="#t344">344</a></span><span class="t"> <span class="com"># Special case: if today is Monday (result=0), we want next Monday (7 days), not today.</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t345" href="#t345">345</a></span><span class="t"> <span class="key">if</span> <span class="nam">week_only</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t346" href="#t346">346</a></span><span class="t"> <span class="nam">days_until_monday</span> <span class="op">=</span> <span class="op">(</span><span class="num">7</span> <span class="op">-</span> <span class="nam">today</span><span class="op">.</span><span class="nam">weekday</span><span class="op">(</span><span class="op">)</span><span class="op">)</span> <span class="op">%</span> <span class="num">7</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t347" href="#t347">347</a></span><span class="t"> <span class="key">if</span> <span class="nam">days_until_monday</span> <span class="op">==</span> <span class="num">0</span> <span class="key">and</span> <span class="nam">today</span><span class="op">.</span><span class="nam">weekday</span><span class="op">(</span><span class="op">)</span> <span class="op">!=</span> <span class="num">0</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t348" href="#t348">348</a></span><span class="t"> <span class="nam">days_until_monday</span> <span class="op">=</span> <span class="num">7</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t349" href="#t349">349</a></span><span class="t"> <span class="nam">week_start</span> <span class="op">=</span> <span class="nam">today</span> <span class="op">+</span> <span class="nam">timedelta</span><span class="op">(</span><span class="nam">days</span><span class="op">=</span><span class="nam">days_until_monday</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t350" href="#t350">350</a></span><span class="t"> <span class="nam">week_end</span> <span class="op">=</span> <span class="nam">week_start</span> <span class="op">+</span> <span class="nam">timedelta</span><span class="op">(</span><span class="nam">days</span><span class="op">=</span><span class="num">4</span><span class="op">)</span> <span class="com"># Mon-Fri</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t351" href="#t351">351</a></span><span class="t"> <span class="key">else</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t352" href="#t352">352</a></span><span class="t"> <span class="nam">week_end</span> <span class="op">=</span> <span class="nam">today</span> <span class="op">+</span> <span class="nam">timedelta</span><span class="op">(</span><span class="nam">days</span><span class="op">=</span><span class="num">7</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t353" href="#t353">353</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t354" href="#t354">354</a></span><span class="t"> <span class="nam">today_list</span> <span class="op">=</span> <span class="op">[</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t355" href="#t355">355</a></span><span class="t"> <span class="nam">week_list</span> <span class="op">=</span> <span class="op">[</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t356" href="#t356">356</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t357" href="#t357">357</a></span><span class="t"> <span class="key">for</span> <span class="nam">ticker</span><span class="op">,</span> <span class="nam">data</span> <span class="key">in</span> <span class="nam">earnings</span><span class="op">.</span><span class="nam">items</span><span class="op">(</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t358" href="#t358">358</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">data</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"date"</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t359" href="#t359">359</a></span><span class="t"> <span class="key">continue</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t360" href="#t360">360</a></span><span class="t"> <span class="key">try</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t361" href="#t361">361</a></span><span class="t"> <span class="nam">ed</span> <span class="op">=</span> <span class="nam">datetime</span><span class="op">.</span><span class="nam">strptime</span><span class="op">(</span><span class="nam">data</span><span class="op">[</span><span class="str">"date"</span><span class="op">]</span><span class="op">,</span> <span class="str">"%Y-%m-%d"</span><span class="op">)</span><span class="op">.</span><span class="nam">date</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t362" href="#t362">362</a></span><span class="t"> <span class="nam">stock</span> <span class="op">=</span> <span class="nam">next</span><span class="op">(</span><span class="op">(</span><span class="nam">s</span> <span class="key">for</span> <span class="nam">s</span> <span class="key">in</span> <span class="nam">portfolio</span> <span class="key">if</span> <span class="nam">s</span><span class="op">[</span><span class="str">"symbol"</span><span class="op">]</span> <span class="op">==</span> <span class="nam">ticker</span><span class="op">)</span><span class="op">,</span> <span class="key">None</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t363" href="#t363">363</a></span><span class="t"> <span class="nam">name</span> <span class="op">=</span> <span class="nam">stock</span><span class="op">[</span><span class="str">"name"</span><span class="op">]</span> <span class="key">if</span> <span class="nam">stock</span> <span class="key">else</span> <span class="nam">ticker</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t364" href="#t364">364</a></span><span class="t"> <span class="nam">category</span> <span class="op">=</span> <span class="nam">stock</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"category"</span><span class="op">,</span> <span class="str">""</span><span class="op">)</span> <span class="key">if</span> <span class="nam">stock</span> <span class="key">else</span> <span class="str">""</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t365" href="#t365">365</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t366" href="#t366">366</a></span><span class="t"> <span class="nam">entry</span> <span class="op">=</span> <span class="op">{</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t367" href="#t367">367</a></span><span class="t"> <span class="str">"ticker"</span><span class="op">:</span> <span class="nam">ticker</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t368" href="#t368">368</a></span><span class="t"> <span class="str">"name"</span><span class="op">:</span> <span class="nam">name</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t369" href="#t369">369</a></span><span class="t"> <span class="str">"date"</span><span class="op">:</span> <span class="nam">ed</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t370" href="#t370">370</a></span><span class="t"> <span class="str">"time"</span><span class="op">:</span> <span class="nam">data</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"time"</span><span class="op">,</span> <span class="str">""</span><span class="op">)</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t371" href="#t371">371</a></span><span class="t"> <span class="str">"eps_estimate"</span><span class="op">:</span> <span class="nam">data</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"eps_estimate"</span><span class="op">)</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t372" href="#t372">372</a></span><span class="t"> <span class="str">"category"</span><span class="op">:</span> <span class="nam">category</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t373" href="#t373">373</a></span><span class="t"> <span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t374" href="#t374">374</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t375" href="#t375">375</a></span><span class="t"> <span class="key">if</span> <span class="nam">week_only</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t376" href="#t376">376</a></span><span class="t"> <span class="com"># Weekly mode: only show week range</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t377" href="#t377">377</a></span><span class="t"> <span class="key">if</span> <span class="nam">week_start</span> <span class="op"><=</span> <span class="nam">ed</span> <span class="op"><=</span> <span class="nam">week_end</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t378" href="#t378">378</a></span><span class="t"> <span class="nam">week_list</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="nam">entry</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t379" href="#t379">379</a></span><span class="t"> <span class="key">else</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t380" href="#t380">380</a></span><span class="t"> <span class="com"># Daily mode: today + this week</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t381" href="#t381">381</a></span><span class="t"> <span class="key">if</span> <span class="nam">ed</span> <span class="op">==</span> <span class="nam">today</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t382" href="#t382">382</a></span><span class="t"> <span class="nam">today_list</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="nam">entry</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t383" href="#t383">383</a></span><span class="t"> <span class="key">elif</span> <span class="nam">today</span> <span class="op"><</span> <span class="nam">ed</span> <span class="op"><=</span> <span class="nam">week_end</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t384" href="#t384">384</a></span><span class="t"> <span class="nam">week_list</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="nam">entry</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t385" href="#t385">385</a></span><span class="t"> <span class="key">except</span> <span class="nam">ValueError</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t386" href="#t386">386</a></span><span class="t"> <span class="key">continue</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t387" href="#t387">387</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t388" href="#t388">388</a></span><span class="t"> <span class="com"># Handle JSON output</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t389" href="#t389">389</a></span><span class="t"> <span class="key">if</span> <span class="nam">getattr</span><span class="op">(</span><span class="nam">args</span><span class="op">,</span> <span class="str">'json'</span><span class="op">,</span> <span class="key">False</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t390" href="#t390">390</a></span><span class="t"> <span class="key">if</span> <span class="nam">week_only</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t391" href="#t391">391</a></span><span class="t"> <span class="nam">result</span> <span class="op">=</span> <span class="op">{</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t392" href="#t392">392</a></span><span class="t"> <span class="str">"week_start"</span><span class="op">:</span> <span class="nam">week_start</span><span class="op">.</span><span class="nam">isoformat</span><span class="op">(</span><span class="op">)</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t393" href="#t393">393</a></span><span class="t"> <span class="str">"week_end"</span><span class="op">:</span> <span class="nam">week_end</span><span class="op">.</span><span class="nam">isoformat</span><span class="op">(</span><span class="op">)</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t394" href="#t394">394</a></span><span class="t"> <span class="str">"earnings"</span><span class="op">:</span> <span class="op">[</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t395" href="#t395">395</a></span><span class="t"> <span class="op">{</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t396" href="#t396">396</a></span><span class="t"> <span class="str">"ticker"</span><span class="op">:</span> <span class="nam">e</span><span class="op">[</span><span class="str">"ticker"</span><span class="op">]</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t397" href="#t397">397</a></span><span class="t"> <span class="str">"name"</span><span class="op">:</span> <span class="nam">e</span><span class="op">[</span><span class="str">"name"</span><span class="op">]</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t398" href="#t398">398</a></span><span class="t"> <span class="str">"date"</span><span class="op">:</span> <span class="nam">e</span><span class="op">[</span><span class="str">"date"</span><span class="op">]</span><span class="op">.</span><span class="nam">isoformat</span><span class="op">(</span><span class="op">)</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t399" href="#t399">399</a></span><span class="t"> <span class="str">"time"</span><span class="op">:</span> <span class="nam">e</span><span class="op">[</span><span class="str">"time"</span><span class="op">]</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t400" href="#t400">400</a></span><span class="t"> <span class="str">"eps_estimate"</span><span class="op">:</span> <span class="nam">e</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"eps_estimate"</span><span class="op">)</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t401" href="#t401">401</a></span><span class="t"> <span class="str">"category"</span><span class="op">:</span> <span class="nam">e</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"category"</span><span class="op">,</span> <span class="str">""</span><span class="op">)</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t402" href="#t402">402</a></span><span class="t"> <span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t403" href="#t403">403</a></span><span class="t"> <span class="key">for</span> <span class="nam">e</span> <span class="key">in</span> <span class="nam">sorted</span><span class="op">(</span><span class="nam">week_list</span><span class="op">,</span> <span class="nam">key</span><span class="op">=</span><span class="key">lambda</span> <span class="nam">x</span><span class="op">:</span> <span class="nam">x</span><span class="op">[</span><span class="str">"date"</span><span class="op">]</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t404" href="#t404">404</a></span><span class="t"> <span class="op">]</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t405" href="#t405">405</a></span><span class="t"> <span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t406" href="#t406">406</a></span><span class="t"> <span class="key">else</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t407" href="#t407">407</a></span><span class="t"> <span class="nam">result</span> <span class="op">=</span> <span class="op">{</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t408" href="#t408">408</a></span><span class="t"> <span class="str">"today"</span><span class="op">:</span> <span class="op">[</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t409" href="#t409">409</a></span><span class="t"> <span class="op">{</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t410" href="#t410">410</a></span><span class="t"> <span class="str">"ticker"</span><span class="op">:</span> <span class="nam">e</span><span class="op">[</span><span class="str">"ticker"</span><span class="op">]</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t411" href="#t411">411</a></span><span class="t"> <span class="str">"name"</span><span class="op">:</span> <span class="nam">e</span><span class="op">[</span><span class="str">"name"</span><span class="op">]</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t412" href="#t412">412</a></span><span class="t"> <span class="str">"date"</span><span class="op">:</span> <span class="nam">e</span><span class="op">[</span><span class="str">"date"</span><span class="op">]</span><span class="op">.</span><span class="nam">isoformat</span><span class="op">(</span><span class="op">)</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t413" href="#t413">413</a></span><span class="t"> <span class="str">"time"</span><span class="op">:</span> <span class="nam">e</span><span class="op">[</span><span class="str">"time"</span><span class="op">]</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t414" href="#t414">414</a></span><span class="t"> <span class="str">"eps_estimate"</span><span class="op">:</span> <span class="nam">e</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"eps_estimate"</span><span class="op">)</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t415" href="#t415">415</a></span><span class="t"> <span class="str">"category"</span><span class="op">:</span> <span class="nam">e</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"category"</span><span class="op">,</span> <span class="str">""</span><span class="op">)</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t416" href="#t416">416</a></span><span class="t"> <span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t417" href="#t417">417</a></span><span class="t"> <span class="key">for</span> <span class="nam">e</span> <span class="key">in</span> <span class="nam">sorted</span><span class="op">(</span><span class="nam">today_list</span><span class="op">,</span> <span class="nam">key</span><span class="op">=</span><span class="key">lambda</span> <span class="nam">x</span><span class="op">:</span> <span class="nam">x</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"time"</span><span class="op">,</span> <span class="str">"zzz"</span><span class="op">)</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t418" href="#t418">418</a></span><span class="t"> <span class="op">]</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t419" href="#t419">419</a></span><span class="t"> <span class="str">"this_week"</span><span class="op">:</span> <span class="op">[</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t420" href="#t420">420</a></span><span class="t"> <span class="op">{</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t421" href="#t421">421</a></span><span class="t"> <span class="str">"ticker"</span><span class="op">:</span> <span class="nam">e</span><span class="op">[</span><span class="str">"ticker"</span><span class="op">]</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t422" href="#t422">422</a></span><span class="t"> <span class="str">"name"</span><span class="op">:</span> <span class="nam">e</span><span class="op">[</span><span class="str">"name"</span><span class="op">]</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t423" href="#t423">423</a></span><span class="t"> <span class="str">"date"</span><span class="op">:</span> <span class="nam">e</span><span class="op">[</span><span class="str">"date"</span><span class="op">]</span><span class="op">.</span><span class="nam">isoformat</span><span class="op">(</span><span class="op">)</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t424" href="#t424">424</a></span><span class="t"> <span class="str">"time"</span><span class="op">:</span> <span class="nam">e</span><span class="op">[</span><span class="str">"time"</span><span class="op">]</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t425" href="#t425">425</a></span><span class="t"> <span class="str">"eps_estimate"</span><span class="op">:</span> <span class="nam">e</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"eps_estimate"</span><span class="op">)</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t426" href="#t426">426</a></span><span class="t"> <span class="str">"category"</span><span class="op">:</span> <span class="nam">e</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"category"</span><span class="op">,</span> <span class="str">""</span><span class="op">)</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t427" href="#t427">427</a></span><span class="t"> <span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t428" href="#t428">428</a></span><span class="t"> <span class="key">for</span> <span class="nam">e</span> <span class="key">in</span> <span class="nam">sorted</span><span class="op">(</span><span class="nam">week_list</span><span class="op">,</span> <span class="nam">key</span><span class="op">=</span><span class="key">lambda</span> <span class="nam">x</span><span class="op">:</span> <span class="nam">x</span><span class="op">[</span><span class="str">"date"</span><span class="op">]</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t429" href="#t429">429</a></span><span class="t"> <span class="op">]</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t430" href="#t430">430</a></span><span class="t"> <span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t431" href="#t431">431</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="nam">json</span><span class="op">.</span><span class="nam">dumps</span><span class="op">(</span><span class="nam">result</span><span class="op">,</span> <span class="nam">indent</span><span class="op">=</span><span class="num">2</span><span class="op">)</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t432" href="#t432">432</a></span><span class="t"> <span class="key">return</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t433" href="#t433">433</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t434" href="#t434">434</a></span><span class="t"> <span class="com"># Translations</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t435" href="#t435">435</a></span><span class="t"> <span class="nam">lang</span> <span class="op">=</span> <span class="nam">getattr</span><span class="op">(</span><span class="nam">args</span><span class="op">,</span> <span class="str">'lang'</span><span class="op">,</span> <span class="str">'en'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t436" href="#t436">436</a></span><span class="t"> <span class="key">if</span> <span class="nam">lang</span> <span class="op">==</span> <span class="str">"de"</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t437" href="#t437">437</a></span><span class="t"> <span class="nam">labels</span> <span class="op">=</span> <span class="op">{</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t438" href="#t438">438</a></span><span class="t"> <span class="str">"today"</span><span class="op">:</span> <span class="str">"EARNINGS HEUTE"</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t439" href="#t439">439</a></span><span class="t"> <span class="str">"week"</span><span class="op">:</span> <span class="str">"EARNINGS DIESE WOCHE"</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t440" href="#t440">440</a></span><span class="t"> <span class="str">"week_preview"</span><span class="op">:</span> <span class="str">"EARNINGS NÄCHSTE WOCHE"</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t441" href="#t441">441</a></span><span class="t"> <span class="str">"pre"</span><span class="op">:</span> <span class="str">"vor Börseneröffnung"</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t442" href="#t442">442</a></span><span class="t"> <span class="str">"post"</span><span class="op">:</span> <span class="str">"nach Börsenschluss"</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t443" href="#t443">443</a></span><span class="t"> <span class="str">"pre_short"</span><span class="op">:</span> <span class="str">"vor"</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t444" href="#t444">444</a></span><span class="t"> <span class="str">"post_short"</span><span class="op">:</span> <span class="str">"nach"</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t445" href="#t445">445</a></span><span class="t"> <span class="str">"est"</span><span class="op">:</span> <span class="str">"Erw"</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t446" href="#t446">446</a></span><span class="t"> <span class="str">"none"</span><span class="op">:</span> <span class="str">"Keine Earnings diese Woche"</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t447" href="#t447">447</a></span><span class="t"> <span class="str">"none_week"</span><span class="op">:</span> <span class="str">"Keine Earnings nächste Woche"</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t448" href="#t448">448</a></span><span class="t"> <span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t449" href="#t449">449</a></span><span class="t"> <span class="key">else</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t450" href="#t450">450</a></span><span class="t"> <span class="nam">labels</span> <span class="op">=</span> <span class="op">{</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t451" href="#t451">451</a></span><span class="t"> <span class="str">"today"</span><span class="op">:</span> <span class="str">"EARNINGS TODAY"</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t452" href="#t452">452</a></span><span class="t"> <span class="str">"week"</span><span class="op">:</span> <span class="str">"EARNINGS THIS WEEK"</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t453" href="#t453">453</a></span><span class="t"> <span class="str">"week_preview"</span><span class="op">:</span> <span class="str">"EARNINGS NEXT WEEK"</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t454" href="#t454">454</a></span><span class="t"> <span class="str">"pre"</span><span class="op">:</span> <span class="str">"pre-market"</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t455" href="#t455">455</a></span><span class="t"> <span class="str">"post"</span><span class="op">:</span> <span class="str">"after-close"</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t456" href="#t456">456</a></span><span class="t"> <span class="str">"pre_short"</span><span class="op">:</span> <span class="str">"pre"</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t457" href="#t457">457</a></span><span class="t"> <span class="str">"post_short"</span><span class="op">:</span> <span class="str">"post"</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t458" href="#t458">458</a></span><span class="t"> <span class="str">"est"</span><span class="op">:</span> <span class="str">"Est"</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t459" href="#t459">459</a></span><span class="t"> <span class="str">"none"</span><span class="op">:</span> <span class="str">"No earnings this week"</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t460" href="#t460">460</a></span><span class="t"> <span class="str">"none_week"</span><span class="op">:</span> <span class="str">"No earnings next week"</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t461" href="#t461">461</a></span><span class="t"> <span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t462" href="#t462">462</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t463" href="#t463">463</a></span><span class="t"> <span class="com"># Date header</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t464" href="#t464">464</a></span><span class="t"> <span class="nam">date_str</span> <span class="op">=</span> <span class="nam">datetime</span><span class="op">.</span><span class="nam">now</span><span class="op">(</span><span class="op">)</span><span class="op">.</span><span class="nam">strftime</span><span class="op">(</span><span class="str">"%b %d, %Y"</span><span class="op">)</span> <span class="key">if</span> <span class="nam">lang</span> <span class="op">==</span> <span class="str">"en"</span> <span class="key">else</span> <span class="nam">datetime</span><span class="op">.</span><span class="nam">now</span><span class="op">(</span><span class="op">)</span><span class="op">.</span><span class="nam">strftime</span><span class="op">(</span><span class="str">"%d. %b %Y"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t465" href="#t465">465</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t466" href="#t466">466</a></span><span class="t"> <span class="com"># Output for briefing</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t467" href="#t467">467</a></span><span class="t"> <span class="nam">output</span> <span class="op">=</span> <span class="op">[</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t468" href="#t468">468</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t469" href="#t469">469</a></span><span class="t"> <span class="com"># Daily mode: show today's earnings</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t470" href="#t470">470</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">week_only</span> <span class="key">and</span> <span class="nam">today_list</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t471" href="#t471">471</a></span><span class="t"> <span class="nam">output</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="str">f"📅 {labels['today']} — {date_str}\n"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t472" href="#t472">472</a></span><span class="t"> <span class="key">for</span> <span class="nam">e</span> <span class="key">in</span> <span class="nam">sorted</span><span class="op">(</span><span class="nam">today_list</span><span class="op">,</span> <span class="nam">key</span><span class="op">=</span><span class="key">lambda</span> <span class="nam">x</span><span class="op">:</span> <span class="nam">x</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"time"</span><span class="op">,</span> <span class="str">"zzz"</span><span class="op">)</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t473" href="#t473">473</a></span><span class="t"> <span class="nam">time_str</span> <span class="op">=</span> <span class="str">f" ({labels['pre']})"</span> <span class="key">if</span> <span class="nam">e</span><span class="op">[</span><span class="str">"time"</span><span class="op">]</span> <span class="op">==</span> <span class="str">"bmo"</span> <span class="key">else</span> <span class="str">f" ({labels['post']})"</span> <span class="key">if</span> <span class="nam">e</span><span class="op">[</span><span class="str">"time"</span><span class="op">]</span> <span class="op">==</span> <span class="str">"amc"</span> <span class="key">else</span> <span class="str">""</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t474" href="#t474">474</a></span><span class="t"> <span class="nam">eps_str</span> <span class="op">=</span> <span class="str">f" — {labels['est']}: ${e['eps_estimate']:.2f}"</span> <span class="key">if</span> <span class="nam">e</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"eps_estimate"</span><span class="op">)</span> <span class="key">else</span> <span class="str">""</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t475" href="#t475">475</a></span><span class="t"> <span class="nam">output</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="str">f"• {e['ticker']} — {e['name']}{time_str}{eps_str}"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t476" href="#t476">476</a></span><span class="t"> <span class="nam">output</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="str">""</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t477" href="#t477">477</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t478" href="#t478">478</a></span><span class="t"> <span class="key">if</span> <span class="nam">week_list</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t479" href="#t479">479</a></span><span class="t"> <span class="com"># Use different header for weekly preview mode</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t480" href="#t480">480</a></span><span class="t"> <span class="nam">week_label</span> <span class="op">=</span> <span class="nam">labels</span><span class="op">[</span><span class="str">'week_preview'</span><span class="op">]</span> <span class="key">if</span> <span class="nam">week_only</span> <span class="key">else</span> <span class="nam">labels</span><span class="op">[</span><span class="str">'week'</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t481" href="#t481">481</a></span><span class="t"> <span class="key">if</span> <span class="nam">week_only</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t482" href="#t482">482</a></span><span class="t"> <span class="com"># Show date range for weekly preview</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t483" href="#t483">483</a></span><span class="t"> <span class="nam">week_range</span> <span class="op">=</span> <span class="str">f"{week_start.strftime('%b %d')} - {week_end.strftime('%b %d')}"</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t484" href="#t484">484</a></span><span class="t"> <span class="nam">output</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="str">f"📅 {week_label} ({week_range})\n"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t485" href="#t485">485</a></span><span class="t"> <span class="key">else</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t486" href="#t486">486</a></span><span class="t"> <span class="nam">output</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="str">f"📅 {week_label}\n"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t487" href="#t487">487</a></span><span class="t"> <span class="key">for</span> <span class="nam">e</span> <span class="key">in</span> <span class="nam">sorted</span><span class="op">(</span><span class="nam">week_list</span><span class="op">,</span> <span class="nam">key</span><span class="op">=</span><span class="key">lambda</span> <span class="nam">x</span><span class="op">:</span> <span class="nam">x</span><span class="op">[</span><span class="str">"date"</span><span class="op">]</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t488" href="#t488">488</a></span><span class="t"> <span class="nam">day_name</span> <span class="op">=</span> <span class="nam">e</span><span class="op">[</span><span class="str">"date"</span><span class="op">]</span><span class="op">.</span><span class="nam">strftime</span><span class="op">(</span><span class="str">"%a %d.%m"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t489" href="#t489">489</a></span><span class="t"> <span class="nam">time_str</span> <span class="op">=</span> <span class="str">f" ({labels['pre_short']})"</span> <span class="key">if</span> <span class="nam">e</span><span class="op">[</span><span class="str">"time"</span><span class="op">]</span> <span class="op">==</span> <span class="str">"bmo"</span> <span class="key">else</span> <span class="str">f" ({labels['post_short']})"</span> <span class="key">if</span> <span class="nam">e</span><span class="op">[</span><span class="str">"time"</span><span class="op">]</span> <span class="op">==</span> <span class="str">"amc"</span> <span class="key">else</span> <span class="str">""</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t490" href="#t490">490</a></span><span class="t"> <span class="nam">output</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="str">f"• {day_name}: {e['ticker']} — {e['name']}{time_str}"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t491" href="#t491">491</a></span><span class="t"> <span class="nam">output</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="str">""</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t492" href="#t492">492</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t493" href="#t493">493</a></span><span class="t"> <span class="key">if</span> <span class="nam">output</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t494" href="#t494">494</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">"\n"</span><span class="op">.</span><span class="nam">join</span><span class="op">(</span><span class="nam">output</span><span class="op">)</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t495" href="#t495">495</a></span><span class="t"> <span class="key">else</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t496" href="#t496">496</a></span><span class="t"> <span class="key">if</span> <span class="nam">args</span><span class="op">.</span><span class="nam">verbose</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t497" href="#t497">497</a></span><span class="t"> <span class="nam">no_earnings_label</span> <span class="op">=</span> <span class="nam">labels</span><span class="op">[</span><span class="str">'none_week'</span><span class="op">]</span> <span class="key">if</span> <span class="nam">week_only</span> <span class="key">else</span> <span class="nam">labels</span><span class="op">[</span><span class="str">'none'</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t498" href="#t498">498</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"📅 {no_earnings_label}"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t499" href="#t499">499</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t500" href="#t500">500</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t501" href="#t501">501</a></span><span class="t"><span class="key">def</span> <span class="nam">get_briefing_section</span><span class="op">(</span><span class="op">)</span> <span class="op">-></span> <span class="nam">str</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t502" href="#t502">502</a></span><span class="t"> <span class="str">"""Get earnings section for daily briefing (called by briefing.py)."""</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t503" href="#t503">503</a></span><span class="t"> <span class="key">from</span> <span class="nam">io</span> <span class="key">import</span> <span class="nam">StringIO</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t504" href="#t504">504</a></span><span class="t"> <span class="key">import</span> <span class="nam">contextlib</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t505" href="#t505">505</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t506" href="#t506">506</a></span><span class="t"> <span class="com"># Capture check output</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t507" href="#t507">507</a></span><span class="t"> <span class="key">class</span> <span class="nam">Args</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t508" href="#t508">508</a></span><span class="t"> <span class="nam">verbose</span> <span class="op">=</span> <span class="key">False</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t509" href="#t509">509</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t510" href="#t510">510</a></span><span class="t"> <span class="nam">f</span> <span class="op">=</span> <span class="nam">StringIO</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t511" href="#t511">511</a></span><span class="t"> <span class="key">with</span> <span class="nam">contextlib</span><span class="op">.</span><span class="nam">redirect_stdout</span><span class="op">(</span><span class="nam">f</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t512" href="#t512">512</a></span><span class="t"> <span class="nam">check_earnings</span><span class="op">(</span><span class="nam">Args</span><span class="op">(</span><span class="op">)</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t513" href="#t513">513</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t514" href="#t514">514</a></span><span class="t"> <span class="key">return</span> <span class="nam">f</span><span class="op">.</span><span class="nam">getvalue</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t515" href="#t515">515</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t516" href="#t516">516</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t517" href="#t517">517</a></span><span class="t"><span class="key">def</span> <span class="nam">get_earnings_context</span><span class="op">(</span><span class="nam">symbols</span><span class="op">:</span> <span class="nam">list</span><span class="op">[</span><span class="nam">str</span><span class="op">]</span><span class="op">)</span> <span class="op">-></span> <span class="nam">list</span><span class="op">[</span><span class="nam">dict</span><span class="op">]</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t518" href="#t518">518</a></span><span class="t"> <span class="str">"""</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t519" href="#t519">519</a></span><span class="t"><span class="str"> Get recent earnings data (beats/misses) for symbols using OpenBB.</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t520" href="#t520">520</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t521" href="#t521">521</a></span><span class="t"><span class="str"> Returns list of dicts with: symbol, eps_actual, eps_estimate, surprise, revenue_actual, revenue_estimate</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t522" href="#t522">522</a></span><span class="t"><span class="str"> """</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t523" href="#t523">523</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">OPENBB_BINARY</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t524" href="#t524">524</a></span><span class="t"> <span class="key">return</span> <span class="op">[</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t525" href="#t525">525</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t526" href="#t526">526</a></span><span class="t"> <span class="nam">results</span> <span class="op">=</span> <span class="op">[</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t527" href="#t527">527</a></span><span class="t"> <span class="key">for</span> <span class="nam">symbol</span> <span class="key">in</span> <span class="nam">symbols</span><span class="op">[</span><span class="op">:</span><span class="num">10</span><span class="op">]</span><span class="op">:</span> <span class="com"># Limit to 10 symbols</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t528" href="#t528">528</a></span><span class="t"> <span class="key">try</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t529" href="#t529">529</a></span><span class="t"> <span class="nam">result</span> <span class="op">=</span> <span class="nam">subprocess</span><span class="op">.</span><span class="nam">run</span><span class="op">(</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t530" href="#t530">530</a></span><span class="t"> <span class="op">[</span><span class="nam">OPENBB_BINARY</span><span class="op">,</span> <span class="nam">symbol</span><span class="op">,</span> <span class="str">'--earnings'</span><span class="op">]</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t531" href="#t531">531</a></span><span class="t"> <span class="nam">capture_output</span><span class="op">=</span><span class="key">True</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t532" href="#t532">532</a></span><span class="t"> <span class="nam">text</span><span class="op">=</span><span class="key">True</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t533" href="#t533">533</a></span><span class="t"> <span class="nam">timeout</span><span class="op">=</span><span class="num">30</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t534" href="#t534">534</a></span><span class="t"> <span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t535" href="#t535">535</a></span><span class="t"> <span class="key">if</span> <span class="nam">result</span><span class="op">.</span><span class="nam">returncode</span> <span class="op">==</span> <span class="num">0</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t536" href="#t536">536</a></span><span class="t"> <span class="key">try</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t537" href="#t537">537</a></span><span class="t"> <span class="nam">data</span> <span class="op">=</span> <span class="nam">json</span><span class="op">.</span><span class="nam">loads</span><span class="op">(</span><span class="nam">result</span><span class="op">.</span><span class="nam">stdout</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t538" href="#t538">538</a></span><span class="t"> <span class="key">if</span> <span class="nam">isinstance</span><span class="op">(</span><span class="nam">data</span><span class="op">,</span> <span class="nam">list</span><span class="op">)</span> <span class="key">and</span> <span class="nam">data</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t539" href="#t539">539</a></span><span class="t"> <span class="nam">results</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="op">{</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t540" href="#t540">540</a></span><span class="t"> <span class="str">'symbol'</span><span class="op">:</span> <span class="nam">symbol</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t541" href="#t541">541</a></span><span class="t"> <span class="str">'earnings'</span><span class="op">:</span> <span class="nam">data</span><span class="op">[</span><span class="num">0</span><span class="op">]</span> <span class="key">if</span> <span class="nam">isinstance</span><span class="op">(</span><span class="nam">data</span><span class="op">[</span><span class="num">0</span><span class="op">]</span><span class="op">,</span> <span class="nam">dict</span><span class="op">)</span> <span class="key">else</span> <span class="op">{</span><span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t542" href="#t542">542</a></span><span class="t"> <span class="op">}</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t543" href="#t543">543</a></span><span class="t"> <span class="key">except</span> <span class="nam">json</span><span class="op">.</span><span class="nam">JSONDecodeError</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t544" href="#t544">544</a></span><span class="t"> <span class="key">pass</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t545" href="#t545">545</a></span><span class="t"> <span class="key">except</span> <span class="nam">Exception</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t546" href="#t546">546</a></span><span class="t"> <span class="key">pass</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t547" href="#t547">547</a></span><span class="t"> <span class="key">return</span> <span class="nam">results</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t548" href="#t548">548</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t549" href="#t549">549</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t550" href="#t550">550</a></span><span class="t"><span class="key">def</span> <span class="nam">get_analyst_ratings</span><span class="op">(</span><span class="nam">symbols</span><span class="op">:</span> <span class="nam">list</span><span class="op">[</span><span class="nam">str</span><span class="op">]</span><span class="op">)</span> <span class="op">-></span> <span class="nam">list</span><span class="op">[</span><span class="nam">dict</span><span class="op">]</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t551" href="#t551">551</a></span><span class="t"> <span class="str">"""</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t552" href="#t552">552</a></span><span class="t"><span class="str"> Get analyst upgrades/downgrades for symbols using OpenBB.</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t553" href="#t553">553</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t554" href="#t554">554</a></span><span class="t"><span class="str"> Returns list of dicts with: symbol, rating, target_price, firm, direction</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t555" href="#t555">555</a></span><span class="t"><span class="str"> """</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t556" href="#t556">556</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">OPENBB_BINARY</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t557" href="#t557">557</a></span><span class="t"> <span class="key">return</span> <span class="op">[</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t558" href="#t558">558</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t559" href="#t559">559</a></span><span class="t"> <span class="nam">results</span> <span class="op">=</span> <span class="op">[</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t560" href="#t560">560</a></span><span class="t"> <span class="key">for</span> <span class="nam">symbol</span> <span class="key">in</span> <span class="nam">symbols</span><span class="op">[</span><span class="op">:</span><span class="num">10</span><span class="op">]</span><span class="op">:</span> <span class="com"># Limit to 10 symbols</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t561" href="#t561">561</a></span><span class="t"> <span class="key">try</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t562" href="#t562">562</a></span><span class="t"> <span class="nam">result</span> <span class="op">=</span> <span class="nam">subprocess</span><span class="op">.</span><span class="nam">run</span><span class="op">(</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t563" href="#t563">563</a></span><span class="t"> <span class="op">[</span><span class="nam">OPENBB_BINARY</span><span class="op">,</span> <span class="nam">symbol</span><span class="op">,</span> <span class="str">'--rating'</span><span class="op">]</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t564" href="#t564">564</a></span><span class="t"> <span class="nam">capture_output</span><span class="op">=</span><span class="key">True</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t565" href="#t565">565</a></span><span class="t"> <span class="nam">text</span><span class="op">=</span><span class="key">True</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t566" href="#t566">566</a></span><span class="t"> <span class="nam">timeout</span><span class="op">=</span><span class="num">30</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t567" href="#t567">567</a></span><span class="t"> <span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t568" href="#t568">568</a></span><span class="t"> <span class="key">if</span> <span class="nam">result</span><span class="op">.</span><span class="nam">returncode</span> <span class="op">==</span> <span class="num">0</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t569" href="#t569">569</a></span><span class="t"> <span class="key">try</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t570" href="#t570">570</a></span><span class="t"> <span class="nam">data</span> <span class="op">=</span> <span class="nam">json</span><span class="op">.</span><span class="nam">loads</span><span class="op">(</span><span class="nam">result</span><span class="op">.</span><span class="nam">stdout</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t571" href="#t571">571</a></span><span class="t"> <span class="key">if</span> <span class="nam">isinstance</span><span class="op">(</span><span class="nam">data</span><span class="op">,</span> <span class="nam">list</span><span class="op">)</span> <span class="key">and</span> <span class="nam">data</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t572" href="#t572">572</a></span><span class="t"> <span class="nam">results</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="op">{</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t573" href="#t573">573</a></span><span class="t"> <span class="str">'symbol'</span><span class="op">:</span> <span class="nam">symbol</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t574" href="#t574">574</a></span><span class="t"> <span class="str">'rating'</span><span class="op">:</span> <span class="nam">data</span><span class="op">[</span><span class="num">0</span><span class="op">]</span> <span class="key">if</span> <span class="nam">isinstance</span><span class="op">(</span><span class="nam">data</span><span class="op">[</span><span class="num">0</span><span class="op">]</span><span class="op">,</span> <span class="nam">dict</span><span class="op">)</span> <span class="key">else</span> <span class="op">{</span><span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t575" href="#t575">575</a></span><span class="t"> <span class="op">}</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t576" href="#t576">576</a></span><span class="t"> <span class="key">except</span> <span class="nam">json</span><span class="op">.</span><span class="nam">JSONDecodeError</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t577" href="#t577">577</a></span><span class="t"> <span class="key">pass</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t578" href="#t578">578</a></span><span class="t"> <span class="key">except</span> <span class="nam">Exception</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t579" href="#t579">579</a></span><span class="t"> <span class="key">pass</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t580" href="#t580">580</a></span><span class="t"> <span class="key">return</span> <span class="nam">results</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t581" href="#t581">581</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t582" href="#t582">582</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t583" href="#t583">583</a></span><span class="t"><span class="key">def</span> <span class="nam">main</span><span class="op">(</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t584" href="#t584">584</a></span><span class="t"> <span class="nam">parser</span> <span class="op">=</span> <span class="nam">argparse</span><span class="op">.</span><span class="nam">ArgumentParser</span><span class="op">(</span><span class="nam">description</span><span class="op">=</span><span class="str">"Earnings Calendar Tracker"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t585" href="#t585">585</a></span><span class="t"> <span class="nam">subparsers</span> <span class="op">=</span> <span class="nam">parser</span><span class="op">.</span><span class="nam">add_subparsers</span><span class="op">(</span><span class="nam">dest</span><span class="op">=</span><span class="str">"command"</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"Commands"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t586" href="#t586">586</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t587" href="#t587">587</a></span><span class="t"> <span class="com"># list command</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t588" href="#t588">588</a></span><span class="t"> <span class="nam">list_parser</span> <span class="op">=</span> <span class="nam">subparsers</span><span class="op">.</span><span class="nam">add_parser</span><span class="op">(</span><span class="str">"list"</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"List all upcoming earnings"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t589" href="#t589">589</a></span><span class="t"> <span class="nam">list_parser</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">"--refresh"</span><span class="op">,</span> <span class="str">"-r"</span><span class="op">,</span> <span class="nam">action</span><span class="op">=</span><span class="str">"store_true"</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"Force refresh"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t590" href="#t590">590</a></span><span class="t"> <span class="nam">list_parser</span><span class="op">.</span><span class="nam">set_defaults</span><span class="op">(</span><span class="nam">func</span><span class="op">=</span><span class="nam">list_earnings</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t591" href="#t591">591</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t592" href="#t592">592</a></span><span class="t"> <span class="com"># check command</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t593" href="#t593">593</a></span><span class="t"> <span class="nam">check_parser</span> <span class="op">=</span> <span class="nam">subparsers</span><span class="op">.</span><span class="nam">add_parser</span><span class="op">(</span><span class="str">"check"</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"Check today/this week"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t594" href="#t594">594</a></span><span class="t"> <span class="nam">check_parser</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">"--verbose"</span><span class="op">,</span> <span class="str">"-v"</span><span class="op">,</span> <span class="nam">action</span><span class="op">=</span><span class="str">"store_true"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t595" href="#t595">595</a></span><span class="t"> <span class="nam">check_parser</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">"--json"</span><span class="op">,</span> <span class="nam">action</span><span class="op">=</span><span class="str">"store_true"</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"JSON output"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t596" href="#t596">596</a></span><span class="t"> <span class="nam">check_parser</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">"--lang"</span><span class="op">,</span> <span class="nam">default</span><span class="op">=</span><span class="str">"en"</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"Output language (en, de)"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t597" href="#t597">597</a></span><span class="t"> <span class="nam">check_parser</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">"--week"</span><span class="op">,</span> <span class="nam">action</span><span class="op">=</span><span class="str">"store_true"</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"Show full week preview (for weekly cron)"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t598" href="#t598">598</a></span><span class="t"> <span class="nam">check_parser</span><span class="op">.</span><span class="nam">set_defaults</span><span class="op">(</span><span class="nam">func</span><span class="op">=</span><span class="nam">check_earnings</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t599" href="#t599">599</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t600" href="#t600">600</a></span><span class="t"> <span class="com"># refresh command</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t601" href="#t601">601</a></span><span class="t"> <span class="nam">refresh_parser</span> <span class="op">=</span> <span class="nam">subparsers</span><span class="op">.</span><span class="nam">add_parser</span><span class="op">(</span><span class="str">"refresh"</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"Force refresh all data"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t602" href="#t602">602</a></span><span class="t"> <span class="nam">refresh_parser</span><span class="op">.</span><span class="nam">set_defaults</span><span class="op">(</span><span class="nam">func</span><span class="op">=</span><span class="key">lambda</span> <span class="nam">a</span><span class="op">:</span> <span class="nam">refresh_earnings</span><span class="op">(</span><span class="nam">load_portfolio</span><span class="op">(</span><span class="op">)</span><span class="op">,</span> <span class="nam">force</span><span class="op">=</span><span class="key">True</span><span class="op">)</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t603" href="#t603">603</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t604" href="#t604">604</a></span><span class="t"> <span class="nam">args</span> <span class="op">=</span> <span class="nam">parser</span><span class="op">.</span><span class="nam">parse_args</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t605" href="#t605">605</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t606" href="#t606">606</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">args</span><span class="op">.</span><span class="nam">command</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t607" href="#t607">607</a></span><span class="t"> <span class="nam">parser</span><span class="op">.</span><span class="nam">print_help</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t608" href="#t608">608</a></span><span class="t"> <span class="key">return</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t609" href="#t609">609</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t610" href="#t610">610</a></span><span class="t"> <span class="nam">args</span><span class="op">.</span><span class="nam">func</span><span class="op">(</span><span class="nam">args</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t611" href="#t611">611</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t612" href="#t612">612</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="exc show_exc"><span class="n"><a id="t613" href="#t613">613</a></span><span class="t"><span class="key">if</span> <span class="nam">__name__</span> <span class="op">==</span> <span class="str">"__main__"</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="exc show_exc"><span class="n"><a id="t614" href="#t614">614</a></span><span class="t"> <span class="nam">main</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
</main>
|
||||
<footer>
|
||||
<div class="content">
|
||||
<p>
|
||||
<a class="nav" href="z_de1a740d5dc98ffd_briefing_py.html">« prev</a>
|
||||
<a class="nav" href="index.html">^ index</a>
|
||||
<a class="nav" href="z_de1a740d5dc98ffd_fetch_news_py.html">» next</a>
|
||||
|
||||
<a class="nav" href="https://coverage.readthedocs.io/en/7.13.2">coverage.py v7.13.2</a>,
|
||||
created at 2026-02-01 16:34 -0800
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
1223
htmlcov/z_de1a740d5dc98ffd_fetch_news_py.html
generated
Normal file
1223
htmlcov/z_de1a740d5dc98ffd_fetch_news_py.html
generated
Normal file
File diff suppressed because it is too large
Load Diff
414
htmlcov/z_de1a740d5dc98ffd_portfolio_py.html
generated
Normal file
414
htmlcov/z_de1a740d5dc98ffd_portfolio_py.html
generated
Normal file
@@ -0,0 +1,414 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>Coverage for scripts/portfolio.py: 32%</title>
|
||||
<link rel="icon" sizes="32x32" href="favicon_32_cb_c827f16f.png">
|
||||
<link rel="stylesheet" href="style_cb_9ff733b0.css" type="text/css">
|
||||
<script src="coverage_html_cb_dd2e7eb5.js" defer></script>
|
||||
</head>
|
||||
<body class="pyfile">
|
||||
<header>
|
||||
<div class="content">
|
||||
<h1>
|
||||
<span class="text">Coverage for </span><b>scripts / portfolio.py</b>:
|
||||
<span class="pc_cov">32%</span>
|
||||
</h1>
|
||||
<aside id="help_panel_wrapper">
|
||||
<input id="help_panel_state" type="checkbox">
|
||||
<label for="help_panel_state">
|
||||
<img id="keyboard_icon" src="keybd_closed_cb_900cfef5.png" alt="Show/hide keyboard shortcuts">
|
||||
</label>
|
||||
<div id="help_panel">
|
||||
<p class="legend">Shortcuts on this page</p>
|
||||
<div class="keyhelp">
|
||||
<p>
|
||||
<kbd>r</kbd>
|
||||
<kbd>m</kbd>
|
||||
<kbd>x</kbd>
|
||||
toggle line displays
|
||||
</p>
|
||||
<p>
|
||||
<kbd>j</kbd>
|
||||
<kbd>k</kbd>
|
||||
next/prev highlighted chunk
|
||||
</p>
|
||||
<p>
|
||||
<kbd>0</kbd> (zero) top of page
|
||||
</p>
|
||||
<p>
|
||||
<kbd>1</kbd> (one) first highlighted chunk
|
||||
</p>
|
||||
<p>
|
||||
<kbd>[</kbd>
|
||||
<kbd>]</kbd>
|
||||
prev/next file
|
||||
</p>
|
||||
<p>
|
||||
<kbd>u</kbd> up to the index
|
||||
</p>
|
||||
<p>
|
||||
<kbd>?</kbd> show/hide this help
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
<h2>
|
||||
<span class="text">183 statements </span>
|
||||
<button type="button" class="run button_toggle_run" value="run" data-shortcut="r" title="Toggle lines run">59<span class="text"> run</span></button>
|
||||
<button type="button" class="mis show_mis button_toggle_mis" value="mis" data-shortcut="m" title="Toggle lines missing">124<span class="text"> missing</span></button>
|
||||
<button type="button" class="exc show_exc button_toggle_exc" value="exc" data-shortcut="x" title="Toggle lines excluded">2<span class="text"> excluded</span></button>
|
||||
</h2>
|
||||
<p class="text">
|
||||
<a id="prevFileLink" class="nav" href="z_de1a740d5dc98ffd_fetch_news_py.html">« prev</a>
|
||||
<a id="indexLink" class="nav" href="index.html">^ index</a>
|
||||
<a id="nextFileLink" class="nav" href="z_de1a740d5dc98ffd_ranking_py.html">» next</a>
|
||||
|
||||
<a class="nav" href="https://coverage.readthedocs.io/en/7.13.2">coverage.py v7.13.2</a>,
|
||||
created at 2026-02-01 16:34 -0800
|
||||
</p>
|
||||
<aside class="hidden">
|
||||
<button type="button" class="button_next_chunk" data-shortcut="j"></button>
|
||||
<button type="button" class="button_prev_chunk" data-shortcut="k"></button>
|
||||
<button type="button" class="button_top_of_page" data-shortcut="0"></button>
|
||||
<button type="button" class="button_first_chunk" data-shortcut="1"></button>
|
||||
<button type="button" class="button_prev_file" data-shortcut="["></button>
|
||||
<button type="button" class="button_next_file" data-shortcut="]"></button>
|
||||
<button type="button" class="button_to_index" data-shortcut="u"></button>
|
||||
<button type="button" class="button_show_hide_help" data-shortcut="?"></button>
|
||||
</aside>
|
||||
</div>
|
||||
</header>
|
||||
<main id="source">
|
||||
<p class="pln"><span class="n"><a id="t1" href="#t1">1</a></span><span class="t"><span class="com">#!/usr/bin/env python3</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t2" href="#t2">2</a></span><span class="t"><span class="str">"""</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t3" href="#t3">3</a></span><span class="t"><span class="str">Portfolio Manager - CRUD operations for stock watchlist.</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t4" href="#t4">4</a></span><span class="t"><span class="str">"""</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t5" href="#t5">5</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t6" href="#t6">6</a></span><span class="t"><span class="key">import</span> <span class="nam">argparse</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t7" href="#t7">7</a></span><span class="t"><span class="key">import</span> <span class="nam">csv</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t8" href="#t8">8</a></span><span class="t"><span class="key">import</span> <span class="nam">sys</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t9" href="#t9">9</a></span><span class="t"><span class="key">from</span> <span class="nam">pathlib</span> <span class="key">import</span> <span class="nam">Path</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t10" href="#t10">10</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t11" href="#t11">11</a></span><span class="t"><span class="nam">PORTFOLIO_FILE</span> <span class="op">=</span> <span class="nam">Path</span><span class="op">(</span><span class="nam">__file__</span><span class="op">)</span><span class="op">.</span><span class="nam">parent</span><span class="op">.</span><span class="nam">parent</span> <span class="op">/</span> <span class="str">"config"</span> <span class="op">/</span> <span class="str">"portfolio.csv"</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t12" href="#t12">12</a></span><span class="t"><span class="nam">REQUIRED_COLUMNS</span> <span class="op">=</span> <span class="op">[</span><span class="str">'symbol'</span><span class="op">,</span> <span class="str">'name'</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t13" href="#t13">13</a></span><span class="t"><span class="nam">DEFAULT_COLUMNS</span> <span class="op">=</span> <span class="op">[</span><span class="str">'symbol'</span><span class="op">,</span> <span class="str">'name'</span><span class="op">,</span> <span class="str">'category'</span><span class="op">,</span> <span class="str">'notes'</span><span class="op">,</span> <span class="str">'type'</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t14" href="#t14">14</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t15" href="#t15">15</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t16" href="#t16">16</a></span><span class="t"><span class="key">def</span> <span class="nam">validate_portfolio_csv</span><span class="op">(</span><span class="nam">path</span><span class="op">:</span> <span class="nam">Path</span><span class="op">)</span> <span class="op">-></span> <span class="nam">tuple</span><span class="op">[</span><span class="nam">bool</span><span class="op">,</span> <span class="nam">list</span><span class="op">[</span><span class="nam">str</span><span class="op">]</span><span class="op">]</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t17" href="#t17">17</a></span><span class="t"> <span class="str">"""</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t18" href="#t18">18</a></span><span class="t"><span class="str"> Validate portfolio CSV file for common issues.</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t19" href="#t19">19</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t20" href="#t20">20</a></span><span class="t"><span class="str"> Returns:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t21" href="#t21">21</a></span><span class="t"><span class="str"> Tuple of (is_valid, list of warnings)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t22" href="#t22">22</a></span><span class="t"><span class="str"> """</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t23" href="#t23">23</a></span><span class="t"> <span class="nam">warnings</span> <span class="op">=</span> <span class="op">[</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t24" href="#t24">24</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t25" href="#t25">25</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">path</span><span class="op">.</span><span class="nam">exists</span><span class="op">(</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t26" href="#t26">26</a></span><span class="t"> <span class="key">return</span> <span class="key">True</span><span class="op">,</span> <span class="nam">warnings</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t27" href="#t27">27</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t28" href="#t28">28</a></span><span class="t"> <span class="key">try</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t29" href="#t29">29</a></span><span class="t"> <span class="key">with</span> <span class="nam">open</span><span class="op">(</span><span class="nam">path</span><span class="op">,</span> <span class="str">'r'</span><span class="op">,</span> <span class="nam">encoding</span><span class="op">=</span><span class="str">'utf-8'</span><span class="op">)</span> <span class="key">as</span> <span class="nam">f</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t30" href="#t30">30</a></span><span class="t"> <span class="com"># Check for encoding issues</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t31" href="#t31">31</a></span><span class="t"> <span class="nam">content</span> <span class="op">=</span> <span class="nam">f</span><span class="op">.</span><span class="nam">read</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t32" href="#t32">32</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t33" href="#t33">33</a></span><span class="t"> <span class="key">with</span> <span class="nam">open</span><span class="op">(</span><span class="nam">path</span><span class="op">,</span> <span class="str">'r'</span><span class="op">,</span> <span class="nam">encoding</span><span class="op">=</span><span class="str">'utf-8'</span><span class="op">)</span> <span class="key">as</span> <span class="nam">f</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t34" href="#t34">34</a></span><span class="t"> <span class="nam">reader</span> <span class="op">=</span> <span class="nam">csv</span><span class="op">.</span><span class="nam">DictReader</span><span class="op">(</span><span class="nam">f</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t35" href="#t35">35</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t36" href="#t36">36</a></span><span class="t"> <span class="com"># Check required columns</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t37" href="#t37">37</a></span><span class="t"> <span class="key">if</span> <span class="nam">reader</span><span class="op">.</span><span class="nam">fieldnames</span> <span class="key">is</span> <span class="key">None</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t38" href="#t38">38</a></span><span class="t"> <span class="nam">warnings</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="str">"CSV appears to be empty"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t39" href="#t39">39</a></span><span class="t"> <span class="key">return</span> <span class="key">False</span><span class="op">,</span> <span class="nam">warnings</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t40" href="#t40">40</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t41" href="#t41">41</a></span><span class="t"> <span class="nam">missing_cols</span> <span class="op">=</span> <span class="nam">set</span><span class="op">(</span><span class="nam">REQUIRED_COLUMNS</span><span class="op">)</span> <span class="op">-</span> <span class="nam">set</span><span class="op">(</span><span class="nam">reader</span><span class="op">.</span><span class="nam">fieldnames</span> <span class="key">or</span> <span class="op">[</span><span class="op">]</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t42" href="#t42">42</a></span><span class="t"> <span class="key">if</span> <span class="nam">missing_cols</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t43" href="#t43">43</a></span><span class="t"> <span class="nam">warnings</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="str">f"Missing required columns: {', '.join(missing_cols)}"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t44" href="#t44">44</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t45" href="#t45">45</a></span><span class="t"> <span class="com"># Check for duplicate symbols</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t46" href="#t46">46</a></span><span class="t"> <span class="nam">symbols</span> <span class="op">=</span> <span class="op">[</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t47" href="#t47">47</a></span><span class="t"> <span class="key">for</span> <span class="nam">row</span> <span class="key">in</span> <span class="nam">reader</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t48" href="#t48">48</a></span><span class="t"> <span class="nam">symbol</span> <span class="op">=</span> <span class="nam">row</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'symbol'</span><span class="op">,</span> <span class="str">''</span><span class="op">)</span><span class="op">.</span><span class="nam">strip</span><span class="op">(</span><span class="op">)</span><span class="op">.</span><span class="nam">upper</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t49" href="#t49">49</a></span><span class="t"> <span class="key">if</span> <span class="nam">symbol</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t50" href="#t50">50</a></span><span class="t"> <span class="nam">symbols</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="nam">symbol</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t51" href="#t51">51</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t52" href="#t52">52</a></span><span class="t"> <span class="nam">duplicates</span> <span class="op">=</span> <span class="op">[</span><span class="nam">s</span> <span class="key">for</span> <span class="nam">s</span> <span class="key">in</span> <span class="nam">set</span><span class="op">(</span><span class="nam">symbols</span><span class="op">)</span> <span class="key">if</span> <span class="nam">symbols</span><span class="op">.</span><span class="nam">count</span><span class="op">(</span><span class="nam">s</span><span class="op">)</span> <span class="op">></span> <span class="num">1</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t53" href="#t53">53</a></span><span class="t"> <span class="key">if</span> <span class="nam">duplicates</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t54" href="#t54">54</a></span><span class="t"> <span class="nam">warnings</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="str">f"Duplicate symbols found: {', '.join(duplicates)}"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t55" href="#t55">55</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t56" href="#t56">56</a></span><span class="t"> <span class="key">except</span> <span class="nam">UnicodeDecodeError</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t57" href="#t57">57</a></span><span class="t"> <span class="nam">warnings</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="str">"File encoding issue - try saving as UTF-8"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t58" href="#t58">58</a></span><span class="t"> <span class="key">except</span> <span class="nam">Exception</span> <span class="key">as</span> <span class="nam">e</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t59" href="#t59">59</a></span><span class="t"> <span class="nam">warnings</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="str">f"Error reading portfolio: {e}"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t60" href="#t60">60</a></span><span class="t"> <span class="key">return</span> <span class="key">False</span><span class="op">,</span> <span class="nam">warnings</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t61" href="#t61">61</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t62" href="#t62">62</a></span><span class="t"> <span class="key">return</span> <span class="key">True</span><span class="op">,</span> <span class="nam">warnings</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t63" href="#t63">63</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t64" href="#t64">64</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t65" href="#t65">65</a></span><span class="t"><span class="key">def</span> <span class="nam">load_portfolio</span><span class="op">(</span><span class="op">)</span> <span class="op">-></span> <span class="nam">list</span><span class="op">[</span><span class="nam">dict</span><span class="op">]</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t66" href="#t66">66</a></span><span class="t"> <span class="str">"""Load portfolio from CSV with validation."""</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t67" href="#t67">67</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">PORTFOLIO_FILE</span><span class="op">.</span><span class="nam">exists</span><span class="op">(</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t68" href="#t68">68</a></span><span class="t"> <span class="key">return</span> <span class="op">[</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t69" href="#t69">69</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t70" href="#t70">70</a></span><span class="t"> <span class="com"># Validate first</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t71" href="#t71">71</a></span><span class="t"> <span class="nam">is_valid</span><span class="op">,</span> <span class="nam">warnings</span> <span class="op">=</span> <span class="nam">validate_portfolio_csv</span><span class="op">(</span><span class="nam">PORTFOLIO_FILE</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t72" href="#t72">72</a></span><span class="t"> <span class="key">for</span> <span class="nam">warning</span> <span class="key">in</span> <span class="nam">warnings</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t73" href="#t73">73</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"⚠️ Portfolio warning: {warning}"</span><span class="op">,</span> <span class="nam">file</span><span class="op">=</span><span class="nam">sys</span><span class="op">.</span><span class="nam">stderr</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t74" href="#t74">74</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t75" href="#t75">75</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">is_valid</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t76" href="#t76">76</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">"⚠️ Portfolio has errors - returning empty"</span><span class="op">,</span> <span class="nam">file</span><span class="op">=</span><span class="nam">sys</span><span class="op">.</span><span class="nam">stderr</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t77" href="#t77">77</a></span><span class="t"> <span class="key">return</span> <span class="op">[</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t78" href="#t78">78</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t79" href="#t79">79</a></span><span class="t"> <span class="key">try</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t80" href="#t80">80</a></span><span class="t"> <span class="key">with</span> <span class="nam">open</span><span class="op">(</span><span class="nam">PORTFOLIO_FILE</span><span class="op">,</span> <span class="str">'r'</span><span class="op">,</span> <span class="nam">encoding</span><span class="op">=</span><span class="str">'utf-8'</span><span class="op">)</span> <span class="key">as</span> <span class="nam">f</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t81" href="#t81">81</a></span><span class="t"> <span class="nam">reader</span> <span class="op">=</span> <span class="nam">csv</span><span class="op">.</span><span class="nam">DictReader</span><span class="op">(</span><span class="nam">f</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t82" href="#t82">82</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t83" href="#t83">83</a></span><span class="t"> <span class="com"># Normalize data</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t84" href="#t84">84</a></span><span class="t"> <span class="nam">portfolio</span> <span class="op">=</span> <span class="op">[</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t85" href="#t85">85</a></span><span class="t"> <span class="nam">seen_symbols</span> <span class="op">=</span> <span class="nam">set</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t86" href="#t86">86</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t87" href="#t87">87</a></span><span class="t"> <span class="key">for</span> <span class="nam">row</span> <span class="key">in</span> <span class="nam">reader</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t88" href="#t88">88</a></span><span class="t"> <span class="nam">symbol</span> <span class="op">=</span> <span class="nam">row</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'symbol'</span><span class="op">,</span> <span class="str">''</span><span class="op">)</span><span class="op">.</span><span class="nam">strip</span><span class="op">(</span><span class="op">)</span><span class="op">.</span><span class="nam">upper</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t89" href="#t89">89</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">symbol</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t90" href="#t90">90</a></span><span class="t"> <span class="key">continue</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t91" href="#t91">91</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t92" href="#t92">92</a></span><span class="t"> <span class="com"># Skip duplicates (keep first occurrence)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t93" href="#t93">93</a></span><span class="t"> <span class="key">if</span> <span class="nam">symbol</span> <span class="key">in</span> <span class="nam">seen_symbols</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t94" href="#t94">94</a></span><span class="t"> <span class="key">continue</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t95" href="#t95">95</a></span><span class="t"> <span class="nam">seen_symbols</span><span class="op">.</span><span class="nam">add</span><span class="op">(</span><span class="nam">symbol</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t96" href="#t96">96</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t97" href="#t97">97</a></span><span class="t"> <span class="nam">portfolio</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="op">{</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t98" href="#t98">98</a></span><span class="t"> <span class="str">'symbol'</span><span class="op">:</span> <span class="nam">symbol</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t99" href="#t99">99</a></span><span class="t"> <span class="str">'name'</span><span class="op">:</span> <span class="nam">row</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'name'</span><span class="op">,</span> <span class="nam">symbol</span><span class="op">)</span> <span class="key">or</span> <span class="nam">symbol</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t100" href="#t100">100</a></span><span class="t"> <span class="str">'category'</span><span class="op">:</span> <span class="nam">row</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'category'</span><span class="op">,</span> <span class="str">''</span><span class="op">)</span> <span class="key">or</span> <span class="str">''</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t101" href="#t101">101</a></span><span class="t"> <span class="str">'notes'</span><span class="op">:</span> <span class="nam">row</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'notes'</span><span class="op">,</span> <span class="str">''</span><span class="op">)</span> <span class="key">or</span> <span class="str">''</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t102" href="#t102">102</a></span><span class="t"> <span class="str">'type'</span><span class="op">:</span> <span class="nam">row</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'type'</span><span class="op">,</span> <span class="str">'Watchlist'</span><span class="op">)</span> <span class="key">or</span> <span class="str">'Watchlist'</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t103" href="#t103">103</a></span><span class="t"> <span class="op">}</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t104" href="#t104">104</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t105" href="#t105">105</a></span><span class="t"> <span class="key">return</span> <span class="nam">portfolio</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t106" href="#t106">106</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t107" href="#t107">107</a></span><span class="t"> <span class="key">except</span> <span class="nam">Exception</span> <span class="key">as</span> <span class="nam">e</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t108" href="#t108">108</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"⚠️ Error loading portfolio: {e}"</span><span class="op">,</span> <span class="nam">file</span><span class="op">=</span><span class="nam">sys</span><span class="op">.</span><span class="nam">stderr</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t109" href="#t109">109</a></span><span class="t"> <span class="key">return</span> <span class="op">[</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t110" href="#t110">110</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t111" href="#t111">111</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t112" href="#t112">112</a></span><span class="t"><span class="key">def</span> <span class="nam">save_portfolio</span><span class="op">(</span><span class="nam">portfolio</span><span class="op">:</span> <span class="nam">list</span><span class="op">[</span><span class="nam">dict</span><span class="op">]</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t113" href="#t113">113</a></span><span class="t"> <span class="str">"""Save portfolio to CSV."""</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t114" href="#t114">114</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">portfolio</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t115" href="#t115">115</a></span><span class="t"> <span class="nam">PORTFOLIO_FILE</span><span class="op">.</span><span class="nam">write_text</span><span class="op">(</span><span class="str">"symbol,name,category,notes,type\n"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t116" href="#t116">116</a></span><span class="t"> <span class="key">return</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t117" href="#t117">117</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t118" href="#t118">118</a></span><span class="t"> <span class="key">with</span> <span class="nam">open</span><span class="op">(</span><span class="nam">PORTFOLIO_FILE</span><span class="op">,</span> <span class="str">'w'</span><span class="op">,</span> <span class="nam">newline</span><span class="op">=</span><span class="str">''</span><span class="op">)</span> <span class="key">as</span> <span class="nam">f</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t119" href="#t119">119</a></span><span class="t"> <span class="nam">writer</span> <span class="op">=</span> <span class="nam">csv</span><span class="op">.</span><span class="nam">DictWriter</span><span class="op">(</span><span class="nam">f</span><span class="op">,</span> <span class="nam">fieldnames</span><span class="op">=</span><span class="op">[</span><span class="str">'symbol'</span><span class="op">,</span> <span class="str">'name'</span><span class="op">,</span> <span class="str">'category'</span><span class="op">,</span> <span class="str">'notes'</span><span class="op">,</span> <span class="str">'type'</span><span class="op">]</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t120" href="#t120">120</a></span><span class="t"> <span class="nam">writer</span><span class="op">.</span><span class="nam">writeheader</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t121" href="#t121">121</a></span><span class="t"> <span class="nam">writer</span><span class="op">.</span><span class="nam">writerows</span><span class="op">(</span><span class="nam">portfolio</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t122" href="#t122">122</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t123" href="#t123">123</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t124" href="#t124">124</a></span><span class="t"><span class="key">def</span> <span class="nam">list_portfolio</span><span class="op">(</span><span class="nam">args</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t125" href="#t125">125</a></span><span class="t"> <span class="str">"""List all stocks in portfolio."""</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t126" href="#t126">126</a></span><span class="t"> <span class="nam">portfolio</span> <span class="op">=</span> <span class="nam">load_portfolio</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t127" href="#t127">127</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t128" href="#t128">128</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">portfolio</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t129" href="#t129">129</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">"📂 Portfolio is empty. Use 'portfolio add <SYMBOL>' to add stocks."</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t130" href="#t130">130</a></span><span class="t"> <span class="key">return</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t131" href="#t131">131</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t132" href="#t132">132</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"\n📊 Portfolio ({len(portfolio)} stocks)\n"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t133" href="#t133">133</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t134" href="#t134">134</a></span><span class="t"> <span class="com"># Group by Type then Category</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t135" href="#t135">135</a></span><span class="t"> <span class="nam">by_type</span> <span class="op">=</span> <span class="op">{</span><span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t136" href="#t136">136</a></span><span class="t"> <span class="key">for</span> <span class="nam">stock</span> <span class="key">in</span> <span class="nam">portfolio</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t137" href="#t137">137</a></span><span class="t"> <span class="nam">t</span> <span class="op">=</span> <span class="nam">stock</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'type'</span><span class="op">,</span> <span class="str">'Watchlist'</span><span class="op">)</span> <span class="key">or</span> <span class="str">'Watchlist'</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t138" href="#t138">138</a></span><span class="t"> <span class="key">if</span> <span class="nam">t</span> <span class="key">not</span> <span class="key">in</span> <span class="nam">by_type</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t139" href="#t139">139</a></span><span class="t"> <span class="nam">by_type</span><span class="op">[</span><span class="nam">t</span><span class="op">]</span> <span class="op">=</span> <span class="op">[</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t140" href="#t140">140</a></span><span class="t"> <span class="nam">by_type</span><span class="op">[</span><span class="nam">t</span><span class="op">]</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="nam">stock</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t141" href="#t141">141</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t142" href="#t142">142</a></span><span class="t"> <span class="key">for</span> <span class="nam">t</span><span class="op">,</span> <span class="nam">type_stocks</span> <span class="key">in</span> <span class="nam">by_type</span><span class="op">.</span><span class="nam">items</span><span class="op">(</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t143" href="#t143">143</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"# {t}"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t144" href="#t144">144</a></span><span class="t"> <span class="nam">categories</span> <span class="op">=</span> <span class="op">{</span><span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t145" href="#t145">145</a></span><span class="t"> <span class="key">for</span> <span class="nam">stock</span> <span class="key">in</span> <span class="nam">type_stocks</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t146" href="#t146">146</a></span><span class="t"> <span class="nam">cat</span> <span class="op">=</span> <span class="nam">stock</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'category'</span><span class="op">,</span> <span class="str">'Other'</span><span class="op">)</span> <span class="key">or</span> <span class="str">'Other'</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t147" href="#t147">147</a></span><span class="t"> <span class="key">if</span> <span class="nam">cat</span> <span class="key">not</span> <span class="key">in</span> <span class="nam">categories</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t148" href="#t148">148</a></span><span class="t"> <span class="nam">categories</span><span class="op">[</span><span class="nam">cat</span><span class="op">]</span> <span class="op">=</span> <span class="op">[</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t149" href="#t149">149</a></span><span class="t"> <span class="nam">categories</span><span class="op">[</span><span class="nam">cat</span><span class="op">]</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="nam">stock</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t150" href="#t150">150</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t151" href="#t151">151</a></span><span class="t"> <span class="key">for</span> <span class="nam">cat</span><span class="op">,</span> <span class="nam">stocks</span> <span class="key">in</span> <span class="nam">categories</span><span class="op">.</span><span class="nam">items</span><span class="op">(</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t152" href="#t152">152</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"### {cat}"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t153" href="#t153">153</a></span><span class="t"> <span class="key">for</span> <span class="nam">s</span> <span class="key">in</span> <span class="nam">stocks</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t154" href="#t154">154</a></span><span class="t"> <span class="nam">notes</span> <span class="op">=</span> <span class="str">f" — {s['notes']}"</span> <span class="key">if</span> <span class="nam">s</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'notes'</span><span class="op">)</span> <span class="key">else</span> <span class="str">""</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t155" href="#t155">155</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f" • {s['symbol']}: {s['name']}{notes}"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t156" href="#t156">156</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t157" href="#t157">157</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t158" href="#t158">158</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t159" href="#t159">159</a></span><span class="t"><span class="key">def</span> <span class="nam">add_stock</span><span class="op">(</span><span class="nam">args</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t160" href="#t160">160</a></span><span class="t"> <span class="str">"""Add a stock to portfolio."""</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t161" href="#t161">161</a></span><span class="t"> <span class="nam">portfolio</span> <span class="op">=</span> <span class="nam">load_portfolio</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t162" href="#t162">162</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t163" href="#t163">163</a></span><span class="t"> <span class="com"># Check if already exists</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t164" href="#t164">164</a></span><span class="t"> <span class="key">if</span> <span class="nam">any</span><span class="op">(</span><span class="nam">s</span><span class="op">[</span><span class="str">'symbol'</span><span class="op">]</span><span class="op">.</span><span class="nam">upper</span><span class="op">(</span><span class="op">)</span> <span class="op">==</span> <span class="nam">args</span><span class="op">.</span><span class="nam">symbol</span><span class="op">.</span><span class="nam">upper</span><span class="op">(</span><span class="op">)</span> <span class="key">for</span> <span class="nam">s</span> <span class="key">in</span> <span class="nam">portfolio</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t165" href="#t165">165</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"⚠️ {args.symbol.upper()} already in portfolio"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t166" href="#t166">166</a></span><span class="t"> <span class="key">return</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t167" href="#t167">167</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t168" href="#t168">168</a></span><span class="t"> <span class="nam">new_stock</span> <span class="op">=</span> <span class="op">{</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t169" href="#t169">169</a></span><span class="t"> <span class="str">'symbol'</span><span class="op">:</span> <span class="nam">args</span><span class="op">.</span><span class="nam">symbol</span><span class="op">.</span><span class="nam">upper</span><span class="op">(</span><span class="op">)</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t170" href="#t170">170</a></span><span class="t"> <span class="str">'name'</span><span class="op">:</span> <span class="nam">args</span><span class="op">.</span><span class="nam">name</span> <span class="key">or</span> <span class="nam">args</span><span class="op">.</span><span class="nam">symbol</span><span class="op">.</span><span class="nam">upper</span><span class="op">(</span><span class="op">)</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t171" href="#t171">171</a></span><span class="t"> <span class="str">'category'</span><span class="op">:</span> <span class="nam">args</span><span class="op">.</span><span class="nam">category</span> <span class="key">or</span> <span class="str">''</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t172" href="#t172">172</a></span><span class="t"> <span class="str">'notes'</span><span class="op">:</span> <span class="nam">args</span><span class="op">.</span><span class="nam">notes</span> <span class="key">or</span> <span class="str">''</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t173" href="#t173">173</a></span><span class="t"> <span class="str">'type'</span><span class="op">:</span> <span class="nam">args</span><span class="op">.</span><span class="nam">type</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t174" href="#t174">174</a></span><span class="t"> <span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t175" href="#t175">175</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t176" href="#t176">176</a></span><span class="t"> <span class="nam">portfolio</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="nam">new_stock</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t177" href="#t177">177</a></span><span class="t"> <span class="nam">save_portfolio</span><span class="op">(</span><span class="nam">portfolio</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t178" href="#t178">178</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"✅ Added {args.symbol.upper()} to portfolio ({args.type})"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t179" href="#t179">179</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t180" href="#t180">180</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t181" href="#t181">181</a></span><span class="t"><span class="key">def</span> <span class="nam">remove_stock</span><span class="op">(</span><span class="nam">args</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t182" href="#t182">182</a></span><span class="t"> <span class="str">"""Remove a stock from portfolio."""</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t183" href="#t183">183</a></span><span class="t"> <span class="nam">portfolio</span> <span class="op">=</span> <span class="nam">load_portfolio</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t184" href="#t184">184</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t185" href="#t185">185</a></span><span class="t"> <span class="nam">original_len</span> <span class="op">=</span> <span class="nam">len</span><span class="op">(</span><span class="nam">portfolio</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t186" href="#t186">186</a></span><span class="t"> <span class="nam">portfolio</span> <span class="op">=</span> <span class="op">[</span><span class="nam">s</span> <span class="key">for</span> <span class="nam">s</span> <span class="key">in</span> <span class="nam">portfolio</span> <span class="key">if</span> <span class="nam">s</span><span class="op">[</span><span class="str">'symbol'</span><span class="op">]</span><span class="op">.</span><span class="nam">upper</span><span class="op">(</span><span class="op">)</span> <span class="op">!=</span> <span class="nam">args</span><span class="op">.</span><span class="nam">symbol</span><span class="op">.</span><span class="nam">upper</span><span class="op">(</span><span class="op">)</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t187" href="#t187">187</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t188" href="#t188">188</a></span><span class="t"> <span class="key">if</span> <span class="nam">len</span><span class="op">(</span><span class="nam">portfolio</span><span class="op">)</span> <span class="op">==</span> <span class="nam">original_len</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t189" href="#t189">189</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"⚠️ {args.symbol.upper()} not found in portfolio"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t190" href="#t190">190</a></span><span class="t"> <span class="key">return</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t191" href="#t191">191</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t192" href="#t192">192</a></span><span class="t"> <span class="nam">save_portfolio</span><span class="op">(</span><span class="nam">portfolio</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t193" href="#t193">193</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"✅ Removed {args.symbol.upper()} from portfolio"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t194" href="#t194">194</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t195" href="#t195">195</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t196" href="#t196">196</a></span><span class="t"><span class="key">def</span> <span class="nam">import_csv</span><span class="op">(</span><span class="nam">args</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t197" href="#t197">197</a></span><span class="t"> <span class="str">"""Import portfolio from external CSV."""</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t198" href="#t198">198</a></span><span class="t"> <span class="nam">import_path</span> <span class="op">=</span> <span class="nam">Path</span><span class="op">(</span><span class="nam">args</span><span class="op">.</span><span class="nam">file</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t199" href="#t199">199</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t200" href="#t200">200</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">import_path</span><span class="op">.</span><span class="nam">exists</span><span class="op">(</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t201" href="#t201">201</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"❌ File not found: {args.file}"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t202" href="#t202">202</a></span><span class="t"> <span class="nam">sys</span><span class="op">.</span><span class="nam">exit</span><span class="op">(</span><span class="num">1</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t203" href="#t203">203</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t204" href="#t204">204</a></span><span class="t"> <span class="key">with</span> <span class="nam">open</span><span class="op">(</span><span class="nam">import_path</span><span class="op">,</span> <span class="str">'r'</span><span class="op">)</span> <span class="key">as</span> <span class="nam">f</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t205" href="#t205">205</a></span><span class="t"> <span class="nam">reader</span> <span class="op">=</span> <span class="nam">csv</span><span class="op">.</span><span class="nam">DictReader</span><span class="op">(</span><span class="nam">f</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t206" href="#t206">206</a></span><span class="t"> <span class="nam">imported</span> <span class="op">=</span> <span class="nam">list</span><span class="op">(</span><span class="nam">reader</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t207" href="#t207">207</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t208" href="#t208">208</a></span><span class="t"> <span class="com"># Normalize fields</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t209" href="#t209">209</a></span><span class="t"> <span class="nam">normalized</span> <span class="op">=</span> <span class="op">[</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t210" href="#t210">210</a></span><span class="t"> <span class="key">for</span> <span class="nam">row</span> <span class="key">in</span> <span class="nam">imported</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t211" href="#t211">211</a></span><span class="t"> <span class="nam">normalized</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="op">{</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t212" href="#t212">212</a></span><span class="t"> <span class="str">'symbol'</span><span class="op">:</span> <span class="nam">row</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'symbol'</span><span class="op">,</span> <span class="nam">row</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'Symbol'</span><span class="op">,</span> <span class="nam">row</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'ticker'</span><span class="op">,</span> <span class="str">''</span><span class="op">)</span><span class="op">)</span><span class="op">)</span><span class="op">.</span><span class="nam">upper</span><span class="op">(</span><span class="op">)</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t213" href="#t213">213</a></span><span class="t"> <span class="str">'name'</span><span class="op">:</span> <span class="nam">row</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'name'</span><span class="op">,</span> <span class="nam">row</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'Name'</span><span class="op">,</span> <span class="nam">row</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'company'</span><span class="op">,</span> <span class="str">''</span><span class="op">)</span><span class="op">)</span><span class="op">)</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t214" href="#t214">214</a></span><span class="t"> <span class="str">'category'</span><span class="op">:</span> <span class="nam">row</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'category'</span><span class="op">,</span> <span class="nam">row</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'Category'</span><span class="op">,</span> <span class="nam">row</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'sector'</span><span class="op">,</span> <span class="str">''</span><span class="op">)</span><span class="op">)</span><span class="op">)</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t215" href="#t215">215</a></span><span class="t"> <span class="str">'notes'</span><span class="op">:</span> <span class="nam">row</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'notes'</span><span class="op">,</span> <span class="nam">row</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'Notes'</span><span class="op">,</span> <span class="str">''</span><span class="op">)</span><span class="op">)</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t216" href="#t216">216</a></span><span class="t"> <span class="str">'type'</span><span class="op">:</span> <span class="nam">row</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'type'</span><span class="op">,</span> <span class="str">'Watchlist'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t217" href="#t217">217</a></span><span class="t"> <span class="op">}</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t218" href="#t218">218</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t219" href="#t219">219</a></span><span class="t"> <span class="nam">save_portfolio</span><span class="op">(</span><span class="nam">normalized</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t220" href="#t220">220</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"✅ Imported {len(normalized)} stocks from {args.file}"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t221" href="#t221">221</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t222" href="#t222">222</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t223" href="#t223">223</a></span><span class="t"><span class="key">def</span> <span class="nam">create_interactive</span><span class="op">(</span><span class="nam">args</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t224" href="#t224">224</a></span><span class="t"> <span class="str">"""Interactive portfolio creation."""</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t225" href="#t225">225</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">"\n📊 Portfolio Creator\n"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t226" href="#t226">226</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">"Enter stocks one per line (format: SYMBOL or SYMBOL,Name,Category)"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t227" href="#t227">227</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">"Type 'done' when finished.\n"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t228" href="#t228">228</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t229" href="#t229">229</a></span><span class="t"> <span class="nam">portfolio</span> <span class="op">=</span> <span class="op">[</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t230" href="#t230">230</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t231" href="#t231">231</a></span><span class="t"> <span class="key">while</span> <span class="key">True</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t232" href="#t232">232</a></span><span class="t"> <span class="key">try</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t233" href="#t233">233</a></span><span class="t"> <span class="nam">line</span> <span class="op">=</span> <span class="nam">input</span><span class="op">(</span><span class="str">"> "</span><span class="op">)</span><span class="op">.</span><span class="nam">strip</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t234" href="#t234">234</a></span><span class="t"> <span class="key">except</span> <span class="op">(</span><span class="nam">EOFError</span><span class="op">,</span> <span class="nam">KeyboardInterrupt</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t235" href="#t235">235</a></span><span class="t"> <span class="key">break</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t236" href="#t236">236</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t237" href="#t237">237</a></span><span class="t"> <span class="key">if</span> <span class="nam">line</span><span class="op">.</span><span class="nam">lower</span><span class="op">(</span><span class="op">)</span> <span class="op">==</span> <span class="str">'done'</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t238" href="#t238">238</a></span><span class="t"> <span class="key">break</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t239" href="#t239">239</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t240" href="#t240">240</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">line</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t241" href="#t241">241</a></span><span class="t"> <span class="key">continue</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t242" href="#t242">242</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t243" href="#t243">243</a></span><span class="t"> <span class="nam">parts</span> <span class="op">=</span> <span class="nam">line</span><span class="op">.</span><span class="nam">split</span><span class="op">(</span><span class="str">','</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t244" href="#t244">244</a></span><span class="t"> <span class="nam">symbol</span> <span class="op">=</span> <span class="nam">parts</span><span class="op">[</span><span class="num">0</span><span class="op">]</span><span class="op">.</span><span class="nam">strip</span><span class="op">(</span><span class="op">)</span><span class="op">.</span><span class="nam">upper</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t245" href="#t245">245</a></span><span class="t"> <span class="nam">name</span> <span class="op">=</span> <span class="nam">parts</span><span class="op">[</span><span class="num">1</span><span class="op">]</span><span class="op">.</span><span class="nam">strip</span><span class="op">(</span><span class="op">)</span> <span class="key">if</span> <span class="nam">len</span><span class="op">(</span><span class="nam">parts</span><span class="op">)</span> <span class="op">></span> <span class="num">1</span> <span class="key">else</span> <span class="nam">symbol</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t246" href="#t246">246</a></span><span class="t"> <span class="nam">category</span> <span class="op">=</span> <span class="nam">parts</span><span class="op">[</span><span class="num">2</span><span class="op">]</span><span class="op">.</span><span class="nam">strip</span><span class="op">(</span><span class="op">)</span> <span class="key">if</span> <span class="nam">len</span><span class="op">(</span><span class="nam">parts</span><span class="op">)</span> <span class="op">></span> <span class="num">2</span> <span class="key">else</span> <span class="str">''</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t247" href="#t247">247</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t248" href="#t248">248</a></span><span class="t"> <span class="nam">portfolio</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="op">{</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t249" href="#t249">249</a></span><span class="t"> <span class="str">'symbol'</span><span class="op">:</span> <span class="nam">symbol</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t250" href="#t250">250</a></span><span class="t"> <span class="str">'name'</span><span class="op">:</span> <span class="nam">name</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t251" href="#t251">251</a></span><span class="t"> <span class="str">'category'</span><span class="op">:</span> <span class="nam">category</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t252" href="#t252">252</a></span><span class="t"> <span class="str">'notes'</span><span class="op">:</span> <span class="str">''</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t253" href="#t253">253</a></span><span class="t"> <span class="str">'type'</span><span class="op">:</span> <span class="str">'Watchlist'</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t254" href="#t254">254</a></span><span class="t"> <span class="op">}</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t255" href="#t255">255</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f" Added: {symbol}"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t256" href="#t256">256</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t257" href="#t257">257</a></span><span class="t"> <span class="key">if</span> <span class="nam">portfolio</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t258" href="#t258">258</a></span><span class="t"> <span class="nam">save_portfolio</span><span class="op">(</span><span class="nam">portfolio</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t259" href="#t259">259</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"\n✅ Created portfolio with {len(portfolio)} stocks"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t260" href="#t260">260</a></span><span class="t"> <span class="key">else</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t261" href="#t261">261</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">"\n⚠️ No stocks added"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t262" href="#t262">262</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t263" href="#t263">263</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t264" href="#t264">264</a></span><span class="t"><span class="key">def</span> <span class="nam">get_symbols</span><span class="op">(</span><span class="nam">args</span><span class="op">=</span><span class="key">None</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t265" href="#t265">265</a></span><span class="t"> <span class="str">"""Get list of symbols (for other scripts to use)."""</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t266" href="#t266">266</a></span><span class="t"> <span class="nam">portfolio</span> <span class="op">=</span> <span class="nam">load_portfolio</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t267" href="#t267">267</a></span><span class="t"> <span class="nam">symbols</span> <span class="op">=</span> <span class="op">[</span><span class="nam">s</span><span class="op">[</span><span class="str">'symbol'</span><span class="op">]</span> <span class="key">for</span> <span class="nam">s</span> <span class="key">in</span> <span class="nam">portfolio</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t268" href="#t268">268</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t269" href="#t269">269</a></span><span class="t"> <span class="key">if</span> <span class="nam">args</span> <span class="key">and</span> <span class="nam">args</span><span class="op">.</span><span class="nam">json</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t270" href="#t270">270</a></span><span class="t"> <span class="key">import</span> <span class="nam">json</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t271" href="#t271">271</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="nam">json</span><span class="op">.</span><span class="nam">dumps</span><span class="op">(</span><span class="nam">symbols</span><span class="op">)</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t272" href="#t272">272</a></span><span class="t"> <span class="key">else</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t273" href="#t273">273</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">','</span><span class="op">.</span><span class="nam">join</span><span class="op">(</span><span class="nam">symbols</span><span class="op">)</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t274" href="#t274">274</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t275" href="#t275">275</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t276" href="#t276">276</a></span><span class="t"><span class="key">def</span> <span class="nam">main</span><span class="op">(</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t277" href="#t277">277</a></span><span class="t"> <span class="nam">parser</span> <span class="op">=</span> <span class="nam">argparse</span><span class="op">.</span><span class="nam">ArgumentParser</span><span class="op">(</span><span class="nam">description</span><span class="op">=</span><span class="str">'Portfolio Manager'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t278" href="#t278">278</a></span><span class="t"> <span class="nam">subparsers</span> <span class="op">=</span> <span class="nam">parser</span><span class="op">.</span><span class="nam">add_subparsers</span><span class="op">(</span><span class="nam">dest</span><span class="op">=</span><span class="str">'command'</span><span class="op">,</span> <span class="nam">required</span><span class="op">=</span><span class="key">True</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t279" href="#t279">279</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t280" href="#t280">280</a></span><span class="t"> <span class="com"># List command</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t281" href="#t281">281</a></span><span class="t"> <span class="nam">list_parser</span> <span class="op">=</span> <span class="nam">subparsers</span><span class="op">.</span><span class="nam">add_parser</span><span class="op">(</span><span class="str">'list'</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">'List portfolio'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t282" href="#t282">282</a></span><span class="t"> <span class="nam">list_parser</span><span class="op">.</span><span class="nam">set_defaults</span><span class="op">(</span><span class="nam">func</span><span class="op">=</span><span class="nam">list_portfolio</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t283" href="#t283">283</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t284" href="#t284">284</a></span><span class="t"> <span class="com"># Add command</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t285" href="#t285">285</a></span><span class="t"> <span class="nam">add_parser</span> <span class="op">=</span> <span class="nam">subparsers</span><span class="op">.</span><span class="nam">add_parser</span><span class="op">(</span><span class="str">'add'</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">'Add stock'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t286" href="#t286">286</a></span><span class="t"> <span class="nam">add_parser</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">'symbol'</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">'Stock symbol'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t287" href="#t287">287</a></span><span class="t"> <span class="nam">add_parser</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">'--name'</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">'Company name'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t288" href="#t288">288</a></span><span class="t"> <span class="nam">add_parser</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">'--category'</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">'Category (e.g., Tech, Finance)'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t289" href="#t289">289</a></span><span class="t"> <span class="nam">add_parser</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">'--notes'</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">'Notes'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t290" href="#t290">290</a></span><span class="t"> <span class="nam">add_parser</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">'--type'</span><span class="op">,</span> <span class="nam">choices</span><span class="op">=</span><span class="op">[</span><span class="str">'Holding'</span><span class="op">,</span> <span class="str">'Watchlist'</span><span class="op">]</span><span class="op">,</span> <span class="nam">default</span><span class="op">=</span><span class="str">'Watchlist'</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">'Portfolio type'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t291" href="#t291">291</a></span><span class="t"> <span class="nam">add_parser</span><span class="op">.</span><span class="nam">set_defaults</span><span class="op">(</span><span class="nam">func</span><span class="op">=</span><span class="nam">add_stock</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t292" href="#t292">292</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t293" href="#t293">293</a></span><span class="t"> <span class="com"># Remove command</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t294" href="#t294">294</a></span><span class="t"> <span class="nam">remove_parser</span> <span class="op">=</span> <span class="nam">subparsers</span><span class="op">.</span><span class="nam">add_parser</span><span class="op">(</span><span class="str">'remove'</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">'Remove stock'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t295" href="#t295">295</a></span><span class="t"> <span class="nam">remove_parser</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">'symbol'</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">'Stock symbol'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t296" href="#t296">296</a></span><span class="t"> <span class="nam">remove_parser</span><span class="op">.</span><span class="nam">set_defaults</span><span class="op">(</span><span class="nam">func</span><span class="op">=</span><span class="nam">remove_stock</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t297" href="#t297">297</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t298" href="#t298">298</a></span><span class="t"> <span class="com"># Import command</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t299" href="#t299">299</a></span><span class="t"> <span class="nam">import_parser</span> <span class="op">=</span> <span class="nam">subparsers</span><span class="op">.</span><span class="nam">add_parser</span><span class="op">(</span><span class="str">'import'</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">'Import from CSV'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t300" href="#t300">300</a></span><span class="t"> <span class="nam">import_parser</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">'file'</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">'CSV file path'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t301" href="#t301">301</a></span><span class="t"> <span class="nam">import_parser</span><span class="op">.</span><span class="nam">set_defaults</span><span class="op">(</span><span class="nam">func</span><span class="op">=</span><span class="nam">import_csv</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t302" href="#t302">302</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t303" href="#t303">303</a></span><span class="t"> <span class="com"># Create command</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t304" href="#t304">304</a></span><span class="t"> <span class="nam">create_parser</span> <span class="op">=</span> <span class="nam">subparsers</span><span class="op">.</span><span class="nam">add_parser</span><span class="op">(</span><span class="str">'create'</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">'Interactive creation'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t305" href="#t305">305</a></span><span class="t"> <span class="nam">create_parser</span><span class="op">.</span><span class="nam">set_defaults</span><span class="op">(</span><span class="nam">func</span><span class="op">=</span><span class="nam">create_interactive</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t306" href="#t306">306</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t307" href="#t307">307</a></span><span class="t"> <span class="com"># Symbols command (for other scripts)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t308" href="#t308">308</a></span><span class="t"> <span class="nam">symbols_parser</span> <span class="op">=</span> <span class="nam">subparsers</span><span class="op">.</span><span class="nam">add_parser</span><span class="op">(</span><span class="str">'symbols'</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">'Get symbols list'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t309" href="#t309">309</a></span><span class="t"> <span class="nam">symbols_parser</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">'--json'</span><span class="op">,</span> <span class="nam">action</span><span class="op">=</span><span class="str">'store_true'</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">'Output as JSON'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t310" href="#t310">310</a></span><span class="t"> <span class="nam">symbols_parser</span><span class="op">.</span><span class="nam">set_defaults</span><span class="op">(</span><span class="nam">func</span><span class="op">=</span><span class="nam">get_symbols</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t311" href="#t311">311</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t312" href="#t312">312</a></span><span class="t"> <span class="nam">args</span> <span class="op">=</span> <span class="nam">parser</span><span class="op">.</span><span class="nam">parse_args</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t313" href="#t313">313</a></span><span class="t"> <span class="nam">args</span><span class="op">.</span><span class="nam">func</span><span class="op">(</span><span class="nam">args</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t314" href="#t314">314</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t315" href="#t315">315</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="exc show_exc"><span class="n"><a id="t316" href="#t316">316</a></span><span class="t"><span class="key">if</span> <span class="nam">__name__</span> <span class="op">==</span> <span class="str">'__main__'</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="exc show_exc"><span class="n"><a id="t317" href="#t317">317</a></span><span class="t"> <span class="nam">main</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
</main>
|
||||
<footer>
|
||||
<div class="content">
|
||||
<p>
|
||||
<a class="nav" href="z_de1a740d5dc98ffd_fetch_news_py.html">« prev</a>
|
||||
<a class="nav" href="index.html">^ index</a>
|
||||
<a class="nav" href="z_de1a740d5dc98ffd_ranking_py.html">» next</a>
|
||||
|
||||
<a class="nav" href="https://coverage.readthedocs.io/en/7.13.2">coverage.py v7.13.2</a>,
|
||||
created at 2026-02-01 16:34 -0800
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
422
htmlcov/z_de1a740d5dc98ffd_ranking_py.html
generated
Normal file
422
htmlcov/z_de1a740d5dc98ffd_ranking_py.html
generated
Normal file
@@ -0,0 +1,422 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>Coverage for scripts/ranking.py: 86%</title>
|
||||
<link rel="icon" sizes="32x32" href="favicon_32_cb_c827f16f.png">
|
||||
<link rel="stylesheet" href="style_cb_9ff733b0.css" type="text/css">
|
||||
<script src="coverage_html_cb_dd2e7eb5.js" defer></script>
|
||||
</head>
|
||||
<body class="pyfile">
|
||||
<header>
|
||||
<div class="content">
|
||||
<h1>
|
||||
<span class="text">Coverage for </span><b>scripts / ranking.py</b>:
|
||||
<span class="pc_cov">86%</span>
|
||||
</h1>
|
||||
<aside id="help_panel_wrapper">
|
||||
<input id="help_panel_state" type="checkbox">
|
||||
<label for="help_panel_state">
|
||||
<img id="keyboard_icon" src="keybd_closed_cb_900cfef5.png" alt="Show/hide keyboard shortcuts">
|
||||
</label>
|
||||
<div id="help_panel">
|
||||
<p class="legend">Shortcuts on this page</p>
|
||||
<div class="keyhelp">
|
||||
<p>
|
||||
<kbd>r</kbd>
|
||||
<kbd>m</kbd>
|
||||
<kbd>x</kbd>
|
||||
toggle line displays
|
||||
</p>
|
||||
<p>
|
||||
<kbd>j</kbd>
|
||||
<kbd>k</kbd>
|
||||
next/prev highlighted chunk
|
||||
</p>
|
||||
<p>
|
||||
<kbd>0</kbd> (zero) top of page
|
||||
</p>
|
||||
<p>
|
||||
<kbd>1</kbd> (one) first highlighted chunk
|
||||
</p>
|
||||
<p>
|
||||
<kbd>[</kbd>
|
||||
<kbd>]</kbd>
|
||||
prev/next file
|
||||
</p>
|
||||
<p>
|
||||
<kbd>u</kbd> up to the index
|
||||
</p>
|
||||
<p>
|
||||
<kbd>?</kbd> show/hide this help
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
<h2>
|
||||
<span class="text">147 statements </span>
|
||||
<button type="button" class="run button_toggle_run" value="run" data-shortcut="r" title="Toggle lines run">126<span class="text"> run</span></button>
|
||||
<button type="button" class="mis show_mis button_toggle_mis" value="mis" data-shortcut="m" title="Toggle lines missing">21<span class="text"> missing</span></button>
|
||||
<button type="button" class="exc show_exc button_toggle_exc" value="exc" data-shortcut="x" title="Toggle lines excluded">9<span class="text"> excluded</span></button>
|
||||
</h2>
|
||||
<p class="text">
|
||||
<a id="prevFileLink" class="nav" href="z_de1a740d5dc98ffd_portfolio_py.html">« prev</a>
|
||||
<a id="indexLink" class="nav" href="index.html">^ index</a>
|
||||
<a id="nextFileLink" class="nav" href="z_de1a740d5dc98ffd_research_py.html">» next</a>
|
||||
|
||||
<a class="nav" href="https://coverage.readthedocs.io/en/7.13.2">coverage.py v7.13.2</a>,
|
||||
created at 2026-02-01 16:34 -0800
|
||||
</p>
|
||||
<aside class="hidden">
|
||||
<button type="button" class="button_next_chunk" data-shortcut="j"></button>
|
||||
<button type="button" class="button_prev_chunk" data-shortcut="k"></button>
|
||||
<button type="button" class="button_top_of_page" data-shortcut="0"></button>
|
||||
<button type="button" class="button_first_chunk" data-shortcut="1"></button>
|
||||
<button type="button" class="button_prev_file" data-shortcut="["></button>
|
||||
<button type="button" class="button_next_file" data-shortcut="]"></button>
|
||||
<button type="button" class="button_to_index" data-shortcut="u"></button>
|
||||
<button type="button" class="button_show_hide_help" data-shortcut="?"></button>
|
||||
</aside>
|
||||
</div>
|
||||
</header>
|
||||
<main id="source">
|
||||
<p class="pln"><span class="n"><a id="t1" href="#t1">1</a></span><span class="t"><span class="com">#!/usr/bin/env python3</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t2" href="#t2">2</a></span><span class="t"><span class="str">"""</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t3" href="#t3">3</a></span><span class="t"><span class="str">Deterministic Headline Ranking - Impact-based ranking policy.</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t4" href="#t4">4</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t5" href="#t5">5</a></span><span class="t"><span class="str">Implements #53: Deterministic impact-based ranking for headline selection.</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t6" href="#t6">6</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t7" href="#t7">7</a></span><span class="t"><span class="str">Scoring Rubric (weights):</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t8" href="#t8">8</a></span><span class="t"><span class="str">- Market Impact (40%): CB decisions, earnings, sanctions, oil spikes</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t9" href="#t9">9</a></span><span class="t"><span class="str">- Novelty (20%): New vs recycled news</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t10" href="#t10">10</a></span><span class="t"><span class="str">- Breadth (20%): Sector-wide vs single-stock</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t11" href="#t11">11</a></span><span class="t"><span class="str">- Credibility (10%): Source reliability</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t12" href="#t12">12</a></span><span class="t"><span class="str">- Diversity Bonus (10%): Underrepresented categories</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t13" href="#t13">13</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t14" href="#t14">14</a></span><span class="t"><span class="str">Output:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t15" href="#t15">15</a></span><span class="t"><span class="str">- MUST_READ: Top 5 stories</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t16" href="#t16">16</a></span><span class="t"><span class="str">- SCAN: 3-5 additional stories (if quality threshold met)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t17" href="#t17">17</a></span><span class="t"><span class="str">"""</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t18" href="#t18">18</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t19" href="#t19">19</a></span><span class="t"><span class="key">import</span> <span class="nam">re</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t20" href="#t20">20</a></span><span class="t"><span class="key">from</span> <span class="nam">datetime</span> <span class="key">import</span> <span class="nam">datetime</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t21" href="#t21">21</a></span><span class="t"><span class="key">from</span> <span class="nam">difflib</span> <span class="key">import</span> <span class="nam">SequenceMatcher</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t22" href="#t22">22</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t23" href="#t23">23</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t24" href="#t24">24</a></span><span class="t"><span class="com"># Category keywords for classification</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t25" href="#t25">25</a></span><span class="t"><span class="nam">CATEGORY_KEYWORDS</span> <span class="op">=</span> <span class="op">{</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t26" href="#t26">26</a></span><span class="t"> <span class="str">"macro"</span><span class="op">:</span> <span class="op">[</span><span class="str">"fed"</span><span class="op">,</span> <span class="str">"ecb"</span><span class="op">,</span> <span class="str">"boj"</span><span class="op">,</span> <span class="str">"central bank"</span><span class="op">,</span> <span class="str">"rate"</span><span class="op">,</span> <span class="str">"inflation"</span><span class="op">,</span> <span class="str">"gdp"</span><span class="op">,</span> <span class="str">"unemployment"</span><span class="op">,</span> <span class="str">"treasury"</span><span class="op">,</span> <span class="str">"yield"</span><span class="op">,</span> <span class="str">"bond"</span><span class="op">]</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t27" href="#t27">27</a></span><span class="t"> <span class="str">"equities"</span><span class="op">:</span> <span class="op">[</span><span class="str">"earnings"</span><span class="op">,</span> <span class="str">"revenue"</span><span class="op">,</span> <span class="str">"profit"</span><span class="op">,</span> <span class="str">"eps"</span><span class="op">,</span> <span class="str">"guidance"</span><span class="op">,</span> <span class="str">"beat"</span><span class="op">,</span> <span class="str">"miss"</span><span class="op">,</span> <span class="str">"upgrade"</span><span class="op">,</span> <span class="str">"downgrade"</span><span class="op">,</span> <span class="str">"target"</span><span class="op">]</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t28" href="#t28">28</a></span><span class="t"> <span class="str">"geopolitics"</span><span class="op">:</span> <span class="op">[</span><span class="str">"sanction"</span><span class="op">,</span> <span class="str">"tariff"</span><span class="op">,</span> <span class="str">"war"</span><span class="op">,</span> <span class="str">"conflict"</span><span class="op">,</span> <span class="str">"embargo"</span><span class="op">,</span> <span class="str">"trump"</span><span class="op">,</span> <span class="str">"china"</span><span class="op">,</span> <span class="str">"russia"</span><span class="op">,</span> <span class="str">"ukraine"</span><span class="op">,</span> <span class="str">"iran"</span><span class="op">,</span> <span class="str">"trade war"</span><span class="op">]</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t29" href="#t29">29</a></span><span class="t"> <span class="str">"energy"</span><span class="op">:</span> <span class="op">[</span><span class="str">"oil"</span><span class="op">,</span> <span class="str">"opec"</span><span class="op">,</span> <span class="str">"crude"</span><span class="op">,</span> <span class="str">"gas"</span><span class="op">,</span> <span class="str">"energy"</span><span class="op">,</span> <span class="str">"brent"</span><span class="op">,</span> <span class="str">"wti"</span><span class="op">]</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t30" href="#t30">30</a></span><span class="t"> <span class="str">"tech"</span><span class="op">:</span> <span class="op">[</span><span class="str">"ai"</span><span class="op">,</span> <span class="str">"chip"</span><span class="op">,</span> <span class="str">"semiconductor"</span><span class="op">,</span> <span class="str">"nvidia"</span><span class="op">,</span> <span class="str">"apple"</span><span class="op">,</span> <span class="str">"google"</span><span class="op">,</span> <span class="str">"microsoft"</span><span class="op">,</span> <span class="str">"meta"</span><span class="op">,</span> <span class="str">"amazon"</span><span class="op">]</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t31" href="#t31">31</a></span><span class="t"><span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t32" href="#t32">32</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t33" href="#t33">33</a></span><span class="t"><span class="com"># Source credibility scores (0-1)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t34" href="#t34">34</a></span><span class="t"><span class="nam">SOURCE_CREDIBILITY</span> <span class="op">=</span> <span class="op">{</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t35" href="#t35">35</a></span><span class="t"> <span class="str">"Wall Street Journal"</span><span class="op">:</span> <span class="num">0.95</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t36" href="#t36">36</a></span><span class="t"> <span class="str">"WSJ"</span><span class="op">:</span> <span class="num">0.95</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t37" href="#t37">37</a></span><span class="t"> <span class="str">"Bloomberg"</span><span class="op">:</span> <span class="num">0.95</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t38" href="#t38">38</a></span><span class="t"> <span class="str">"Reuters"</span><span class="op">:</span> <span class="num">0.90</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t39" href="#t39">39</a></span><span class="t"> <span class="str">"Financial Times"</span><span class="op">:</span> <span class="num">0.90</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t40" href="#t40">40</a></span><span class="t"> <span class="str">"CNBC"</span><span class="op">:</span> <span class="num">0.80</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t41" href="#t41">41</a></span><span class="t"> <span class="str">"Yahoo Finance"</span><span class="op">:</span> <span class="num">0.70</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t42" href="#t42">42</a></span><span class="t"> <span class="str">"MarketWatch"</span><span class="op">:</span> <span class="num">0.75</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t43" href="#t43">43</a></span><span class="t"> <span class="str">"Barron's"</span><span class="op">:</span> <span class="num">0.85</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t44" href="#t44">44</a></span><span class="t"> <span class="str">"Seeking Alpha"</span><span class="op">:</span> <span class="num">0.60</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t45" href="#t45">45</a></span><span class="t"> <span class="str">"Tagesschau"</span><span class="op">:</span> <span class="num">0.85</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t46" href="#t46">46</a></span><span class="t"> <span class="str">"Handelsblatt"</span><span class="op">:</span> <span class="num">0.80</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t47" href="#t47">47</a></span><span class="t"><span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t48" href="#t48">48</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t49" href="#t49">49</a></span><span class="t"><span class="com"># Default config</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t50" href="#t50">50</a></span><span class="t"><span class="nam">DEFAULT_CONFIG</span> <span class="op">=</span> <span class="op">{</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t51" href="#t51">51</a></span><span class="t"> <span class="str">"dedupe_threshold"</span><span class="op">:</span> <span class="num">0.7</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t52" href="#t52">52</a></span><span class="t"> <span class="str">"must_read_count"</span><span class="op">:</span> <span class="num">5</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t53" href="#t53">53</a></span><span class="t"> <span class="str">"scan_count"</span><span class="op">:</span> <span class="num">5</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t54" href="#t54">54</a></span><span class="t"> <span class="str">"must_read_min_score"</span><span class="op">:</span> <span class="num">0.4</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t55" href="#t55">55</a></span><span class="t"> <span class="str">"scan_min_score"</span><span class="op">:</span> <span class="num">0.25</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t56" href="#t56">56</a></span><span class="t"> <span class="str">"source_cap"</span><span class="op">:</span> <span class="num">2</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t57" href="#t57">57</a></span><span class="t"> <span class="str">"weights"</span><span class="op">:</span> <span class="op">{</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t58" href="#t58">58</a></span><span class="t"> <span class="str">"market_impact"</span><span class="op">:</span> <span class="num">0.40</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t59" href="#t59">59</a></span><span class="t"> <span class="str">"novelty"</span><span class="op">:</span> <span class="num">0.20</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t60" href="#t60">60</a></span><span class="t"> <span class="str">"breadth"</span><span class="op">:</span> <span class="num">0.20</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t61" href="#t61">61</a></span><span class="t"> <span class="str">"credibility"</span><span class="op">:</span> <span class="num">0.10</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t62" href="#t62">62</a></span><span class="t"> <span class="str">"diversity"</span><span class="op">:</span> <span class="num">0.10</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t63" href="#t63">63</a></span><span class="t"> <span class="op">}</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t64" href="#t64">64</a></span><span class="t"><span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t65" href="#t65">65</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t66" href="#t66">66</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t67" href="#t67">67</a></span><span class="t"><span class="key">def</span> <span class="nam">normalize_title</span><span class="op">(</span><span class="nam">title</span><span class="op">:</span> <span class="nam">str</span><span class="op">)</span> <span class="op">-></span> <span class="nam">str</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t68" href="#t68">68</a></span><span class="t"> <span class="str">"""Normalize title for comparison."""</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t69" href="#t69">69</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">title</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t70" href="#t70">70</a></span><span class="t"> <span class="key">return</span> <span class="str">""</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t71" href="#t71">71</a></span><span class="t"> <span class="nam">cleaned</span> <span class="op">=</span> <span class="nam">re</span><span class="op">.</span><span class="nam">sub</span><span class="op">(</span><span class="str">r"[^a-z0-9\s]"</span><span class="op">,</span> <span class="str">" "</span><span class="op">,</span> <span class="nam">title</span><span class="op">.</span><span class="nam">lower</span><span class="op">(</span><span class="op">)</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t72" href="#t72">72</a></span><span class="t"> <span class="nam">tokens</span> <span class="op">=</span> <span class="nam">cleaned</span><span class="op">.</span><span class="nam">split</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t73" href="#t73">73</a></span><span class="t"> <span class="key">return</span> <span class="str">" "</span><span class="op">.</span><span class="nam">join</span><span class="op">(</span><span class="nam">tokens</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t74" href="#t74">74</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t75" href="#t75">75</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t76" href="#t76">76</a></span><span class="t"><span class="key">def</span> <span class="nam">title_similarity</span><span class="op">(</span><span class="nam">a</span><span class="op">:</span> <span class="nam">str</span><span class="op">,</span> <span class="nam">b</span><span class="op">:</span> <span class="nam">str</span><span class="op">)</span> <span class="op">-></span> <span class="nam">float</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t77" href="#t77">77</a></span><span class="t"> <span class="str">"""Calculate title similarity using SequenceMatcher."""</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t78" href="#t78">78</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">a</span> <span class="key">or</span> <span class="key">not</span> <span class="nam">b</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t79" href="#t79">79</a></span><span class="t"> <span class="key">return</span> <span class="num">0.0</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t80" href="#t80">80</a></span><span class="t"> <span class="key">return</span> <span class="nam">SequenceMatcher</span><span class="op">(</span><span class="key">None</span><span class="op">,</span> <span class="nam">normalize_title</span><span class="op">(</span><span class="nam">a</span><span class="op">)</span><span class="op">,</span> <span class="nam">normalize_title</span><span class="op">(</span><span class="nam">b</span><span class="op">)</span><span class="op">)</span><span class="op">.</span><span class="nam">ratio</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t81" href="#t81">81</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t82" href="#t82">82</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t83" href="#t83">83</a></span><span class="t"><span class="key">def</span> <span class="nam">deduplicate_headlines</span><span class="op">(</span><span class="nam">headlines</span><span class="op">:</span> <span class="nam">list</span><span class="op">[</span><span class="nam">dict</span><span class="op">]</span><span class="op">,</span> <span class="nam">threshold</span><span class="op">:</span> <span class="nam">float</span> <span class="op">=</span> <span class="num">0.7</span><span class="op">)</span> <span class="op">-></span> <span class="nam">list</span><span class="op">[</span><span class="nam">dict</span><span class="op">]</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t84" href="#t84">84</a></span><span class="t"> <span class="str">"""Remove duplicate headlines by title similarity."""</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t85" href="#t85">85</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">headlines</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t86" href="#t86">86</a></span><span class="t"> <span class="key">return</span> <span class="op">[</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t87" href="#t87">87</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t88" href="#t88">88</a></span><span class="t"> <span class="nam">unique</span> <span class="op">=</span> <span class="op">[</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t89" href="#t89">89</a></span><span class="t"> <span class="key">for</span> <span class="nam">article</span> <span class="key">in</span> <span class="nam">headlines</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t90" href="#t90">90</a></span><span class="t"> <span class="nam">title</span> <span class="op">=</span> <span class="nam">article</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"title"</span><span class="op">,</span> <span class="str">""</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t91" href="#t91">91</a></span><span class="t"> <span class="nam">is_dupe</span> <span class="op">=</span> <span class="key">False</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t92" href="#t92">92</a></span><span class="t"> <span class="key">for</span> <span class="nam">existing</span> <span class="key">in</span> <span class="nam">unique</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t93" href="#t93">93</a></span><span class="t"> <span class="key">if</span> <span class="nam">title_similarity</span><span class="op">(</span><span class="nam">title</span><span class="op">,</span> <span class="nam">existing</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"title"</span><span class="op">,</span> <span class="str">""</span><span class="op">)</span><span class="op">)</span> <span class="op">></span> <span class="nam">threshold</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t94" href="#t94">94</a></span><span class="t"> <span class="nam">is_dupe</span> <span class="op">=</span> <span class="key">True</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t95" href="#t95">95</a></span><span class="t"> <span class="key">break</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t96" href="#t96">96</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">is_dupe</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t97" href="#t97">97</a></span><span class="t"> <span class="nam">unique</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="nam">article</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t98" href="#t98">98</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t99" href="#t99">99</a></span><span class="t"> <span class="key">return</span> <span class="nam">unique</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t100" href="#t100">100</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t101" href="#t101">101</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t102" href="#t102">102</a></span><span class="t"><span class="key">def</span> <span class="nam">classify_category</span><span class="op">(</span><span class="nam">title</span><span class="op">:</span> <span class="nam">str</span><span class="op">,</span> <span class="nam">description</span><span class="op">:</span> <span class="nam">str</span> <span class="op">=</span> <span class="str">""</span><span class="op">)</span> <span class="op">-></span> <span class="nam">list</span><span class="op">[</span><span class="nam">str</span><span class="op">]</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t103" href="#t103">103</a></span><span class="t"> <span class="str">"""Classify headline into categories based on keywords."""</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t104" href="#t104">104</a></span><span class="t"> <span class="nam">text</span> <span class="op">=</span> <span class="str">f"{title} {description}"</span><span class="op">.</span><span class="nam">lower</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t105" href="#t105">105</a></span><span class="t"> <span class="nam">categories</span> <span class="op">=</span> <span class="op">[</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t106" href="#t106">106</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t107" href="#t107">107</a></span><span class="t"> <span class="key">for</span> <span class="nam">category</span><span class="op">,</span> <span class="nam">keywords</span> <span class="key">in</span> <span class="nam">CATEGORY_KEYWORDS</span><span class="op">.</span><span class="nam">items</span><span class="op">(</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t108" href="#t108">108</a></span><span class="t"> <span class="key">for</span> <span class="nam">keyword</span> <span class="key">in</span> <span class="nam">keywords</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t109" href="#t109">109</a></span><span class="t"> <span class="key">if</span> <span class="nam">keyword</span> <span class="key">in</span> <span class="nam">text</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t110" href="#t110">110</a></span><span class="t"> <span class="nam">categories</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="nam">category</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t111" href="#t111">111</a></span><span class="t"> <span class="key">break</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t112" href="#t112">112</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t113" href="#t113">113</a></span><span class="t"> <span class="key">return</span> <span class="nam">categories</span> <span class="key">if</span> <span class="nam">categories</span> <span class="key">else</span> <span class="op">[</span><span class="str">"general"</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t114" href="#t114">114</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t115" href="#t115">115</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t116" href="#t116">116</a></span><span class="t"><span class="key">def</span> <span class="nam">score_market_impact</span><span class="op">(</span><span class="nam">title</span><span class="op">:</span> <span class="nam">str</span><span class="op">,</span> <span class="nam">description</span><span class="op">:</span> <span class="nam">str</span> <span class="op">=</span> <span class="str">""</span><span class="op">)</span> <span class="op">-></span> <span class="nam">float</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t117" href="#t117">117</a></span><span class="t"> <span class="str">"""Score market impact (0-1)."""</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t118" href="#t118">118</a></span><span class="t"> <span class="nam">text</span> <span class="op">=</span> <span class="str">f"{title} {description}"</span><span class="op">.</span><span class="nam">lower</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t119" href="#t119">119</a></span><span class="t"> <span class="nam">score</span> <span class="op">=</span> <span class="num">0.3</span> <span class="com"># Base score</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t120" href="#t120">120</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t121" href="#t121">121</a></span><span class="t"> <span class="com"># High impact indicators</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t122" href="#t122">122</a></span><span class="t"> <span class="nam">high_impact</span> <span class="op">=</span> <span class="op">[</span><span class="str">"fed"</span><span class="op">,</span> <span class="str">"rate cut"</span><span class="op">,</span> <span class="str">"rate hike"</span><span class="op">,</span> <span class="str">"earnings"</span><span class="op">,</span> <span class="str">"guidance"</span><span class="op">,</span> <span class="str">"sanctions"</span><span class="op">,</span> <span class="str">"war"</span><span class="op">,</span> <span class="str">"oil"</span><span class="op">,</span> <span class="str">"recession"</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t123" href="#t123">123</a></span><span class="t"> <span class="key">for</span> <span class="nam">term</span> <span class="key">in</span> <span class="nam">high_impact</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t124" href="#t124">124</a></span><span class="t"> <span class="key">if</span> <span class="nam">term</span> <span class="key">in</span> <span class="nam">text</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t125" href="#t125">125</a></span><span class="t"> <span class="nam">score</span> <span class="op">+=</span> <span class="num">0.15</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t126" href="#t126">126</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t127" href="#t127">127</a></span><span class="t"> <span class="com"># Medium impact</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t128" href="#t128">128</a></span><span class="t"> <span class="nam">medium_impact</span> <span class="op">=</span> <span class="op">[</span><span class="str">"profit"</span><span class="op">,</span> <span class="str">"revenue"</span><span class="op">,</span> <span class="str">"gdp"</span><span class="op">,</span> <span class="str">"inflation"</span><span class="op">,</span> <span class="str">"tariff"</span><span class="op">,</span> <span class="str">"merger"</span><span class="op">,</span> <span class="str">"acquisition"</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t129" href="#t129">129</a></span><span class="t"> <span class="key">for</span> <span class="nam">term</span> <span class="key">in</span> <span class="nam">medium_impact</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t130" href="#t130">130</a></span><span class="t"> <span class="key">if</span> <span class="nam">term</span> <span class="key">in</span> <span class="nam">text</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t131" href="#t131">131</a></span><span class="t"> <span class="nam">score</span> <span class="op">+=</span> <span class="num">0.1</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t132" href="#t132">132</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t133" href="#t133">133</a></span><span class="t"> <span class="key">return</span> <span class="nam">min</span><span class="op">(</span><span class="nam">score</span><span class="op">,</span> <span class="num">1.0</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t134" href="#t134">134</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t135" href="#t135">135</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t136" href="#t136">136</a></span><span class="t"><span class="key">def</span> <span class="nam">score_novelty</span><span class="op">(</span><span class="nam">article</span><span class="op">:</span> <span class="nam">dict</span><span class="op">)</span> <span class="op">-></span> <span class="nam">float</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t137" href="#t137">137</a></span><span class="t"> <span class="str">"""Score novelty based on recency (0-1)."""</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t138" href="#t138">138</a></span><span class="t"> <span class="nam">published_at</span> <span class="op">=</span> <span class="nam">article</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"published_at"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t139" href="#t139">139</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">published_at</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t140" href="#t140">140</a></span><span class="t"> <span class="key">return</span> <span class="num">0.5</span> <span class="com"># Unknown = medium</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t141" href="#t141">141</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t142" href="#t142">142</a></span><span class="t"> <span class="key">try</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t143" href="#t143">143</a></span><span class="t"> <span class="key">if</span> <span class="nam">isinstance</span><span class="op">(</span><span class="nam">published_at</span><span class="op">,</span> <span class="nam">str</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t144" href="#t144">144</a></span><span class="t"> <span class="nam">pub_time</span> <span class="op">=</span> <span class="nam">datetime</span><span class="op">.</span><span class="nam">fromisoformat</span><span class="op">(</span><span class="nam">published_at</span><span class="op">.</span><span class="nam">replace</span><span class="op">(</span><span class="str">"Z"</span><span class="op">,</span> <span class="str">"+00:00"</span><span class="op">)</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t145" href="#t145">145</a></span><span class="t"> <span class="key">else</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t146" href="#t146">146</a></span><span class="t"> <span class="nam">pub_time</span> <span class="op">=</span> <span class="nam">published_at</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t147" href="#t147">147</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t148" href="#t148">148</a></span><span class="t"> <span class="nam">hours_old</span> <span class="op">=</span> <span class="op">(</span><span class="nam">datetime</span><span class="op">.</span><span class="nam">now</span><span class="op">(</span><span class="nam">pub_time</span><span class="op">.</span><span class="nam">tzinfo</span><span class="op">)</span> <span class="op">-</span> <span class="nam">pub_time</span><span class="op">)</span><span class="op">.</span><span class="nam">total_seconds</span><span class="op">(</span><span class="op">)</span> <span class="op">/</span> <span class="num">3600</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t149" href="#t149">149</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t150" href="#t150">150</a></span><span class="t"> <span class="key">if</span> <span class="nam">hours_old</span> <span class="op"><</span> <span class="num">2</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t151" href="#t151">151</a></span><span class="t"> <span class="key">return</span> <span class="num">1.0</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t152" href="#t152">152</a></span><span class="t"> <span class="key">elif</span> <span class="nam">hours_old</span> <span class="op"><</span> <span class="num">6</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t153" href="#t153">153</a></span><span class="t"> <span class="key">return</span> <span class="num">0.8</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t154" href="#t154">154</a></span><span class="t"> <span class="key">elif</span> <span class="nam">hours_old</span> <span class="op"><</span> <span class="num">12</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t155" href="#t155">155</a></span><span class="t"> <span class="key">return</span> <span class="num">0.6</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t156" href="#t156">156</a></span><span class="t"> <span class="key">elif</span> <span class="nam">hours_old</span> <span class="op"><</span> <span class="num">24</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t157" href="#t157">157</a></span><span class="t"> <span class="key">return</span> <span class="num">0.4</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t158" href="#t158">158</a></span><span class="t"> <span class="key">else</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t159" href="#t159">159</a></span><span class="t"> <span class="key">return</span> <span class="num">0.2</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t160" href="#t160">160</a></span><span class="t"> <span class="key">except</span> <span class="nam">Exception</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t161" href="#t161">161</a></span><span class="t"> <span class="key">return</span> <span class="num">0.5</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t162" href="#t162">162</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t163" href="#t163">163</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t164" href="#t164">164</a></span><span class="t"><span class="key">def</span> <span class="nam">score_breadth</span><span class="op">(</span><span class="nam">categories</span><span class="op">:</span> <span class="nam">list</span><span class="op">[</span><span class="nam">str</span><span class="op">]</span><span class="op">)</span> <span class="op">-></span> <span class="nam">float</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t165" href="#t165">165</a></span><span class="t"> <span class="str">"""Score breadth - sector-wide vs single-stock (0-1)."""</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t166" href="#t166">166</a></span><span class="t"> <span class="com"># More categories = broader impact</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t167" href="#t167">167</a></span><span class="t"> <span class="key">if</span> <span class="str">"macro"</span> <span class="key">in</span> <span class="nam">categories</span> <span class="key">or</span> <span class="str">"geopolitics"</span> <span class="key">in</span> <span class="nam">categories</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t168" href="#t168">168</a></span><span class="t"> <span class="key">return</span> <span class="num">0.9</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t169" href="#t169">169</a></span><span class="t"> <span class="key">if</span> <span class="str">"energy"</span> <span class="key">in</span> <span class="nam">categories</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t170" href="#t170">170</a></span><span class="t"> <span class="key">return</span> <span class="num">0.7</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t171" href="#t171">171</a></span><span class="t"> <span class="key">if</span> <span class="nam">len</span><span class="op">(</span><span class="nam">categories</span><span class="op">)</span> <span class="op">></span> <span class="num">1</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t172" href="#t172">172</a></span><span class="t"> <span class="key">return</span> <span class="num">0.6</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t173" href="#t173">173</a></span><span class="t"> <span class="key">return</span> <span class="num">0.4</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t174" href="#t174">174</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t175" href="#t175">175</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t176" href="#t176">176</a></span><span class="t"><span class="key">def</span> <span class="nam">score_credibility</span><span class="op">(</span><span class="nam">source</span><span class="op">:</span> <span class="nam">str</span><span class="op">)</span> <span class="op">-></span> <span class="nam">float</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t177" href="#t177">177</a></span><span class="t"> <span class="str">"""Score source credibility (0-1)."""</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t178" href="#t178">178</a></span><span class="t"> <span class="key">return</span> <span class="nam">SOURCE_CREDIBILITY</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="nam">source</span><span class="op">,</span> <span class="num">0.5</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t179" href="#t179">179</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t180" href="#t180">180</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t181" href="#t181">181</a></span><span class="t"><span class="key">def</span> <span class="nam">calculate_score</span><span class="op">(</span><span class="nam">article</span><span class="op">:</span> <span class="nam">dict</span><span class="op">,</span> <span class="nam">weights</span><span class="op">:</span> <span class="nam">dict</span><span class="op">,</span> <span class="nam">category_counts</span><span class="op">:</span> <span class="nam">dict</span><span class="op">)</span> <span class="op">-></span> <span class="nam">float</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t182" href="#t182">182</a></span><span class="t"> <span class="str">"""Calculate overall score for a headline."""</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t183" href="#t183">183</a></span><span class="t"> <span class="nam">title</span> <span class="op">=</span> <span class="nam">article</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"title"</span><span class="op">,</span> <span class="str">""</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t184" href="#t184">184</a></span><span class="t"> <span class="nam">description</span> <span class="op">=</span> <span class="nam">article</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"description"</span><span class="op">,</span> <span class="str">""</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t185" href="#t185">185</a></span><span class="t"> <span class="nam">source</span> <span class="op">=</span> <span class="nam">article</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"source"</span><span class="op">,</span> <span class="str">""</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t186" href="#t186">186</a></span><span class="t"> <span class="nam">categories</span> <span class="op">=</span> <span class="nam">classify_category</span><span class="op">(</span><span class="nam">title</span><span class="op">,</span> <span class="nam">description</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t187" href="#t187">187</a></span><span class="t"> <span class="nam">article</span><span class="op">[</span><span class="str">"_categories"</span><span class="op">]</span> <span class="op">=</span> <span class="nam">categories</span> <span class="com"># Store for later use</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t188" href="#t188">188</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t189" href="#t189">189</a></span><span class="t"> <span class="com"># Component scores</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t190" href="#t190">190</a></span><span class="t"> <span class="nam">impact</span> <span class="op">=</span> <span class="nam">score_market_impact</span><span class="op">(</span><span class="nam">title</span><span class="op">,</span> <span class="nam">description</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t191" href="#t191">191</a></span><span class="t"> <span class="nam">novelty</span> <span class="op">=</span> <span class="nam">score_novelty</span><span class="op">(</span><span class="nam">article</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t192" href="#t192">192</a></span><span class="t"> <span class="nam">breadth</span> <span class="op">=</span> <span class="nam">score_breadth</span><span class="op">(</span><span class="nam">categories</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t193" href="#t193">193</a></span><span class="t"> <span class="nam">credibility</span> <span class="op">=</span> <span class="nam">score_credibility</span><span class="op">(</span><span class="nam">source</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t194" href="#t194">194</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t195" href="#t195">195</a></span><span class="t"> <span class="com"># Diversity bonus - boost underrepresented categories</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t196" href="#t196">196</a></span><span class="t"> <span class="nam">diversity</span> <span class="op">=</span> <span class="num">0.0</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t197" href="#t197">197</a></span><span class="t"> <span class="key">for</span> <span class="nam">cat</span> <span class="key">in</span> <span class="nam">categories</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t198" href="#t198">198</a></span><span class="t"> <span class="key">if</span> <span class="nam">category_counts</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="nam">cat</span><span class="op">,</span> <span class="num">0</span><span class="op">)</span> <span class="op"><</span> <span class="num">1</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t199" href="#t199">199</a></span><span class="t"> <span class="nam">diversity</span> <span class="op">=</span> <span class="num">0.5</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t200" href="#t200">200</a></span><span class="t"> <span class="key">break</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t201" href="#t201">201</a></span><span class="t"> <span class="key">elif</span> <span class="nam">category_counts</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="nam">cat</span><span class="op">,</span> <span class="num">0</span><span class="op">)</span> <span class="op"><</span> <span class="num">2</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t202" href="#t202">202</a></span><span class="t"> <span class="nam">diversity</span> <span class="op">=</span> <span class="num">0.3</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t203" href="#t203">203</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t204" href="#t204">204</a></span><span class="t"> <span class="com"># Weighted sum</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t205" href="#t205">205</a></span><span class="t"> <span class="nam">score</span> <span class="op">=</span> <span class="op">(</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t206" href="#t206">206</a></span><span class="t"> <span class="nam">impact</span> <span class="op">*</span> <span class="nam">weights</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"market_impact"</span><span class="op">,</span> <span class="num">0.4</span><span class="op">)</span> <span class="op">+</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t207" href="#t207">207</a></span><span class="t"> <span class="nam">novelty</span> <span class="op">*</span> <span class="nam">weights</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"novelty"</span><span class="op">,</span> <span class="num">0.2</span><span class="op">)</span> <span class="op">+</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t208" href="#t208">208</a></span><span class="t"> <span class="nam">breadth</span> <span class="op">*</span> <span class="nam">weights</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"breadth"</span><span class="op">,</span> <span class="num">0.2</span><span class="op">)</span> <span class="op">+</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t209" href="#t209">209</a></span><span class="t"> <span class="nam">credibility</span> <span class="op">*</span> <span class="nam">weights</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"credibility"</span><span class="op">,</span> <span class="num">0.1</span><span class="op">)</span> <span class="op">+</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t210" href="#t210">210</a></span><span class="t"> <span class="nam">diversity</span> <span class="op">*</span> <span class="nam">weights</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"diversity"</span><span class="op">,</span> <span class="num">0.1</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t211" href="#t211">211</a></span><span class="t"> <span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t212" href="#t212">212</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t213" href="#t213">213</a></span><span class="t"> <span class="nam">article</span><span class="op">[</span><span class="str">"_score"</span><span class="op">]</span> <span class="op">=</span> <span class="nam">round</span><span class="op">(</span><span class="nam">score</span><span class="op">,</span> <span class="num">3</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t214" href="#t214">214</a></span><span class="t"> <span class="nam">article</span><span class="op">[</span><span class="str">"_impact"</span><span class="op">]</span> <span class="op">=</span> <span class="nam">round</span><span class="op">(</span><span class="nam">impact</span><span class="op">,</span> <span class="num">3</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t215" href="#t215">215</a></span><span class="t"> <span class="nam">article</span><span class="op">[</span><span class="str">"_novelty"</span><span class="op">]</span> <span class="op">=</span> <span class="nam">round</span><span class="op">(</span><span class="nam">novelty</span><span class="op">,</span> <span class="num">3</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t216" href="#t216">216</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t217" href="#t217">217</a></span><span class="t"> <span class="key">return</span> <span class="nam">score</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t218" href="#t218">218</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t219" href="#t219">219</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t220" href="#t220">220</a></span><span class="t"><span class="key">def</span> <span class="nam">apply_source_cap</span><span class="op">(</span><span class="nam">ranked</span><span class="op">:</span> <span class="nam">list</span><span class="op">[</span><span class="nam">dict</span><span class="op">]</span><span class="op">,</span> <span class="nam">cap</span><span class="op">:</span> <span class="nam">int</span> <span class="op">=</span> <span class="num">2</span><span class="op">)</span> <span class="op">-></span> <span class="nam">list</span><span class="op">[</span><span class="nam">dict</span><span class="op">]</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t221" href="#t221">221</a></span><span class="t"> <span class="str">"""Apply source cap - max N items per outlet."""</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t222" href="#t222">222</a></span><span class="t"> <span class="nam">source_counts</span> <span class="op">=</span> <span class="op">{</span><span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t223" href="#t223">223</a></span><span class="t"> <span class="nam">result</span> <span class="op">=</span> <span class="op">[</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t224" href="#t224">224</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t225" href="#t225">225</a></span><span class="t"> <span class="key">for</span> <span class="nam">article</span> <span class="key">in</span> <span class="nam">ranked</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t226" href="#t226">226</a></span><span class="t"> <span class="nam">source</span> <span class="op">=</span> <span class="nam">article</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"source"</span><span class="op">,</span> <span class="str">"Unknown"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t227" href="#t227">227</a></span><span class="t"> <span class="key">if</span> <span class="nam">source_counts</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="nam">source</span><span class="op">,</span> <span class="num">0</span><span class="op">)</span> <span class="op"><</span> <span class="nam">cap</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t228" href="#t228">228</a></span><span class="t"> <span class="nam">result</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="nam">article</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t229" href="#t229">229</a></span><span class="t"> <span class="nam">source_counts</span><span class="op">[</span><span class="nam">source</span><span class="op">]</span> <span class="op">=</span> <span class="nam">source_counts</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="nam">source</span><span class="op">,</span> <span class="num">0</span><span class="op">)</span> <span class="op">+</span> <span class="num">1</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t230" href="#t230">230</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t231" href="#t231">231</a></span><span class="t"> <span class="key">return</span> <span class="nam">result</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t232" href="#t232">232</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t233" href="#t233">233</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t234" href="#t234">234</a></span><span class="t"><span class="key">def</span> <span class="nam">ensure_diversity</span><span class="op">(</span><span class="nam">selected</span><span class="op">:</span> <span class="nam">list</span><span class="op">[</span><span class="nam">dict</span><span class="op">]</span><span class="op">,</span> <span class="nam">candidates</span><span class="op">:</span> <span class="nam">list</span><span class="op">[</span><span class="nam">dict</span><span class="op">]</span><span class="op">,</span> <span class="nam">required</span><span class="op">:</span> <span class="nam">list</span><span class="op">[</span><span class="nam">str</span><span class="op">]</span><span class="op">)</span> <span class="op">-></span> <span class="nam">list</span><span class="op">[</span><span class="nam">dict</span><span class="op">]</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t235" href="#t235">235</a></span><span class="t"> <span class="str">"""Ensure at least one headline from required categories if available."""</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t236" href="#t236">236</a></span><span class="t"> <span class="nam">result</span> <span class="op">=</span> <span class="nam">list</span><span class="op">(</span><span class="nam">selected</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t237" href="#t237">237</a></span><span class="t"> <span class="nam">covered</span> <span class="op">=</span> <span class="nam">set</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t238" href="#t238">238</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t239" href="#t239">239</a></span><span class="t"> <span class="key">for</span> <span class="nam">article</span> <span class="key">in</span> <span class="nam">result</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t240" href="#t240">240</a></span><span class="t"> <span class="key">for</span> <span class="nam">cat</span> <span class="key">in</span> <span class="nam">article</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"_categories"</span><span class="op">,</span> <span class="op">[</span><span class="op">]</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t241" href="#t241">241</a></span><span class="t"> <span class="nam">covered</span><span class="op">.</span><span class="nam">add</span><span class="op">(</span><span class="nam">cat</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t242" href="#t242">242</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t243" href="#t243">243</a></span><span class="t"> <span class="key">for</span> <span class="nam">req_cat</span> <span class="key">in</span> <span class="nam">required</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t244" href="#t244">244</a></span><span class="t"> <span class="key">if</span> <span class="nam">req_cat</span> <span class="key">not</span> <span class="key">in</span> <span class="nam">covered</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t245" href="#t245">245</a></span><span class="t"> <span class="com"># Find candidate from this category</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t246" href="#t246">246</a></span><span class="t"> <span class="key">for</span> <span class="nam">candidate</span> <span class="key">in</span> <span class="nam">candidates</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t247" href="#t247">247</a></span><span class="t"> <span class="key">if</span> <span class="nam">candidate</span> <span class="key">not</span> <span class="key">in</span> <span class="nam">result</span> <span class="key">and</span> <span class="nam">req_cat</span> <span class="key">in</span> <span class="nam">candidate</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"_categories"</span><span class="op">,</span> <span class="op">[</span><span class="op">]</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t248" href="#t248">248</a></span><span class="t"> <span class="nam">result</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="nam">candidate</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t249" href="#t249">249</a></span><span class="t"> <span class="nam">covered</span><span class="op">.</span><span class="nam">add</span><span class="op">(</span><span class="nam">req_cat</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t250" href="#t250">250</a></span><span class="t"> <span class="key">break</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t251" href="#t251">251</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t252" href="#t252">252</a></span><span class="t"> <span class="key">return</span> <span class="nam">result</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t253" href="#t253">253</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t254" href="#t254">254</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t255" href="#t255">255</a></span><span class="t"><span class="key">def</span> <span class="nam">rank_headlines</span><span class="op">(</span><span class="nam">headlines</span><span class="op">:</span> <span class="nam">list</span><span class="op">[</span><span class="nam">dict</span><span class="op">]</span><span class="op">,</span> <span class="nam">config</span><span class="op">:</span> <span class="nam">dict</span> <span class="op">|</span> <span class="key">None</span> <span class="op">=</span> <span class="key">None</span><span class="op">)</span> <span class="op">-></span> <span class="nam">dict</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t256" href="#t256">256</a></span><span class="t"> <span class="str">"""</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t257" href="#t257">257</a></span><span class="t"><span class="str"> Rank headlines deterministically.</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t258" href="#t258">258</a></span><span class="t"><span class="str"> </span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t259" href="#t259">259</a></span><span class="t"><span class="str"> Args:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t260" href="#t260">260</a></span><span class="t"><span class="str"> headlines: List of headline dicts with title, source, description, etc.</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t261" href="#t261">261</a></span><span class="t"><span class="str"> config: Optional config overrides</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t262" href="#t262">262</a></span><span class="t"><span class="str"> </span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t263" href="#t263">263</a></span><span class="t"><span class="str"> Returns:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t264" href="#t264">264</a></span><span class="t"><span class="str"> {"must_read": [...], "scan": [...]}</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t265" href="#t265">265</a></span><span class="t"><span class="str"> """</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t266" href="#t266">266</a></span><span class="t"> <span class="nam">cfg</span> <span class="op">=</span> <span class="op">{</span><span class="op">**</span><span class="nam">DEFAULT_CONFIG</span><span class="op">,</span> <span class="op">**</span><span class="op">(</span><span class="nam">config</span> <span class="key">or</span> <span class="op">{</span><span class="op">}</span><span class="op">)</span><span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t267" href="#t267">267</a></span><span class="t"> <span class="nam">weights</span> <span class="op">=</span> <span class="nam">cfg</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"weights"</span><span class="op">,</span> <span class="nam">DEFAULT_CONFIG</span><span class="op">[</span><span class="str">"weights"</span><span class="op">]</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t268" href="#t268">268</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t269" href="#t269">269</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">headlines</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t270" href="#t270">270</a></span><span class="t"> <span class="key">return</span> <span class="op">{</span><span class="str">"must_read"</span><span class="op">:</span> <span class="op">[</span><span class="op">]</span><span class="op">,</span> <span class="str">"scan"</span><span class="op">:</span> <span class="op">[</span><span class="op">]</span><span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t271" href="#t271">271</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t272" href="#t272">272</a></span><span class="t"> <span class="com"># Step 1: Deduplicate</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t273" href="#t273">273</a></span><span class="t"> <span class="nam">unique</span> <span class="op">=</span> <span class="nam">deduplicate_headlines</span><span class="op">(</span><span class="nam">headlines</span><span class="op">,</span> <span class="nam">cfg</span><span class="op">[</span><span class="str">"dedupe_threshold"</span><span class="op">]</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t274" href="#t274">274</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t275" href="#t275">275</a></span><span class="t"> <span class="com"># Step 2: Score all headlines</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t276" href="#t276">276</a></span><span class="t"> <span class="nam">category_counts</span> <span class="op">=</span> <span class="op">{</span><span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t277" href="#t277">277</a></span><span class="t"> <span class="key">for</span> <span class="nam">article</span> <span class="key">in</span> <span class="nam">unique</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t278" href="#t278">278</a></span><span class="t"> <span class="nam">calculate_score</span><span class="op">(</span><span class="nam">article</span><span class="op">,</span> <span class="nam">weights</span><span class="op">,</span> <span class="nam">category_counts</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t279" href="#t279">279</a></span><span class="t"> <span class="key">for</span> <span class="nam">cat</span> <span class="key">in</span> <span class="nam">article</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"_categories"</span><span class="op">,</span> <span class="op">[</span><span class="op">]</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t280" href="#t280">280</a></span><span class="t"> <span class="nam">category_counts</span><span class="op">[</span><span class="nam">cat</span><span class="op">]</span> <span class="op">=</span> <span class="nam">category_counts</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="nam">cat</span><span class="op">,</span> <span class="num">0</span><span class="op">)</span> <span class="op">+</span> <span class="num">1</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t281" href="#t281">281</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t282" href="#t282">282</a></span><span class="t"> <span class="com"># Step 3: Sort by score</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t283" href="#t283">283</a></span><span class="t"> <span class="nam">ranked</span> <span class="op">=</span> <span class="nam">sorted</span><span class="op">(</span><span class="nam">unique</span><span class="op">,</span> <span class="nam">key</span><span class="op">=</span><span class="key">lambda</span> <span class="nam">x</span><span class="op">:</span> <span class="nam">x</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"_score"</span><span class="op">,</span> <span class="num">0</span><span class="op">)</span><span class="op">,</span> <span class="nam">reverse</span><span class="op">=</span><span class="key">True</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t284" href="#t284">284</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t285" href="#t285">285</a></span><span class="t"> <span class="com"># Step 4: Apply source cap</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t286" href="#t286">286</a></span><span class="t"> <span class="nam">capped</span> <span class="op">=</span> <span class="nam">apply_source_cap</span><span class="op">(</span><span class="nam">ranked</span><span class="op">,</span> <span class="nam">cfg</span><span class="op">[</span><span class="str">"source_cap"</span><span class="op">]</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t287" href="#t287">287</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t288" href="#t288">288</a></span><span class="t"> <span class="com"># Step 5: Select must_read with diversity quota</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t289" href="#t289">289</a></span><span class="t"> <span class="com"># Leave room for diversity additions by taking count-1 initially</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t290" href="#t290">290</a></span><span class="t"> <span class="nam">must_read_candidates</span> <span class="op">=</span> <span class="op">[</span><span class="nam">a</span> <span class="key">for</span> <span class="nam">a</span> <span class="key">in</span> <span class="nam">capped</span> <span class="key">if</span> <span class="nam">a</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"_score"</span><span class="op">,</span> <span class="num">0</span><span class="op">)</span> <span class="op">>=</span> <span class="nam">cfg</span><span class="op">[</span><span class="str">"must_read_min_score"</span><span class="op">]</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t291" href="#t291">291</a></span><span class="t"> <span class="nam">must_read_count</span> <span class="op">=</span> <span class="nam">cfg</span><span class="op">[</span><span class="str">"must_read_count"</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t292" href="#t292">292</a></span><span class="t"> <span class="nam">must_read</span> <span class="op">=</span> <span class="nam">must_read_candidates</span><span class="op">[</span><span class="op">:</span><span class="nam">max</span><span class="op">(</span><span class="num">1</span><span class="op">,</span> <span class="nam">must_read_count</span> <span class="op">-</span> <span class="num">2</span><span class="op">)</span><span class="op">]</span> <span class="com"># Reserve 2 slots for diversity</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t293" href="#t293">293</a></span><span class="t"> <span class="nam">must_read</span> <span class="op">=</span> <span class="nam">ensure_diversity</span><span class="op">(</span><span class="nam">must_read</span><span class="op">,</span> <span class="nam">capped</span><span class="op">,</span> <span class="op">[</span><span class="str">"macro"</span><span class="op">,</span> <span class="str">"equities"</span><span class="op">,</span> <span class="str">"geopolitics"</span><span class="op">]</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t294" href="#t294">294</a></span><span class="t"> <span class="nam">must_read</span> <span class="op">=</span> <span class="nam">must_read</span><span class="op">[</span><span class="op">:</span><span class="nam">must_read_count</span><span class="op">]</span> <span class="com"># Final trim to exact count</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t295" href="#t295">295</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t296" href="#t296">296</a></span><span class="t"> <span class="com"># Step 6: Select scan (additional items)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t297" href="#t297">297</a></span><span class="t"> <span class="nam">scan_candidates</span> <span class="op">=</span> <span class="op">[</span><span class="nam">a</span> <span class="key">for</span> <span class="nam">a</span> <span class="key">in</span> <span class="nam">capped</span> <span class="key">if</span> <span class="nam">a</span> <span class="key">not</span> <span class="key">in</span> <span class="nam">must_read</span> <span class="key">and</span> <span class="nam">a</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"_score"</span><span class="op">,</span> <span class="num">0</span><span class="op">)</span> <span class="op">>=</span> <span class="nam">cfg</span><span class="op">[</span><span class="str">"scan_min_score"</span><span class="op">]</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t298" href="#t298">298</a></span><span class="t"> <span class="nam">scan</span> <span class="op">=</span> <span class="nam">scan_candidates</span><span class="op">[</span><span class="op">:</span><span class="nam">cfg</span><span class="op">[</span><span class="str">"scan_count"</span><span class="op">]</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t299" href="#t299">299</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t300" href="#t300">300</a></span><span class="t"> <span class="key">return</span> <span class="op">{</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t301" href="#t301">301</a></span><span class="t"> <span class="str">"must_read"</span><span class="op">:</span> <span class="nam">must_read</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t302" href="#t302">302</a></span><span class="t"> <span class="str">"scan"</span><span class="op">:</span> <span class="nam">scan</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t303" href="#t303">303</a></span><span class="t"> <span class="str">"total_processed"</span><span class="op">:</span> <span class="nam">len</span><span class="op">(</span><span class="nam">headlines</span><span class="op">)</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t304" href="#t304">304</a></span><span class="t"> <span class="str">"after_dedupe"</span><span class="op">:</span> <span class="nam">len</span><span class="op">(</span><span class="nam">unique</span><span class="op">)</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t305" href="#t305">305</a></span><span class="t"> <span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t306" href="#t306">306</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t307" href="#t307">307</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="exc show_exc"><span class="n"><a id="t308" href="#t308">308</a></span><span class="t"><span class="key">if</span> <span class="nam">__name__</span> <span class="op">==</span> <span class="str">"__main__"</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t309" href="#t309">309</a></span><span class="t"> <span class="com"># Test with sample data</span> </span><span class="r"></span></p>
|
||||
<p class="exc show_exc"><span class="n"><a id="t310" href="#t310">310</a></span><span class="t"> <span class="nam">test_headlines</span> <span class="op">=</span> <span class="op">[</span> </span><span class="r"></span></p>
|
||||
<p class="exc exc2 show_exc"><span class="n"><a id="t311" href="#t311">311</a></span><span class="t"> <span class="op">{</span><span class="str">"title"</span><span class="op">:</span> <span class="str">"Fed signals rate cut in March"</span><span class="op">,</span> <span class="str">"source"</span><span class="op">:</span> <span class="str">"WSJ"</span><span class="op">,</span> <span class="str">"description"</span><span class="op">:</span> <span class="str">"Federal Reserve hints at policy shift"</span><span class="op">}</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="exc exc2 show_exc"><span class="n"><a id="t312" href="#t312">312</a></span><span class="t"> <span class="op">{</span><span class="str">"title"</span><span class="op">:</span> <span class="str">"Apple earnings beat expectations"</span><span class="op">,</span> <span class="str">"source"</span><span class="op">:</span> <span class="str">"CNBC"</span><span class="op">,</span> <span class="str">"description"</span><span class="op">:</span> <span class="str">"Revenue up 15%"</span><span class="op">}</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="exc exc2 show_exc"><span class="n"><a id="t313" href="#t313">313</a></span><span class="t"> <span class="op">{</span><span class="str">"title"</span><span class="op">:</span> <span class="str">"Oil prices surge on OPEC cuts"</span><span class="op">,</span> <span class="str">"source"</span><span class="op">:</span> <span class="str">"Reuters"</span><span class="op">,</span> <span class="str">"description"</span><span class="op">:</span> <span class="str">"Brent crude hits $90"</span><span class="op">}</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="exc exc2 show_exc"><span class="n"><a id="t314" href="#t314">314</a></span><span class="t"> <span class="op">{</span><span class="str">"title"</span><span class="op">:</span> <span class="str">"China-US trade tensions escalate"</span><span class="op">,</span> <span class="str">"source"</span><span class="op">:</span> <span class="str">"Bloomberg"</span><span class="op">,</span> <span class="str">"description"</span><span class="op">:</span> <span class="str">"New tariffs announced"</span><span class="op">}</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="exc exc2 show_exc"><span class="n"><a id="t315" href="#t315">315</a></span><span class="t"> <span class="op">{</span><span class="str">"title"</span><span class="op">:</span> <span class="str">"Tech stocks rally on AI optimism"</span><span class="op">,</span> <span class="str">"source"</span><span class="op">:</span> <span class="str">"Yahoo Finance"</span><span class="op">,</span> <span class="str">"description"</span><span class="op">:</span> <span class="str">"Nvidia leads gains"</span><span class="op">}</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="exc exc2 show_exc"><span class="n"><a id="t316" href="#t316">316</a></span><span class="t"> <span class="op">{</span><span class="str">"title"</span><span class="op">:</span> <span class="str">"Fed hints at rate reduction"</span><span class="op">,</span> <span class="str">"source"</span><span class="op">:</span> <span class="str">"MarketWatch"</span><span class="op">,</span> <span class="str">"description"</span><span class="op">:</span> <span class="str">"Same story as WSJ"</span><span class="op">}</span><span class="op">,</span> <span class="com"># Dupe</span> </span><span class="r"></span></p>
|
||||
<p class="exc exc2 show_exc"><span class="n"><a id="t317" href="#t317">317</a></span><span class="t"> <span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t318" href="#t318">318</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="exc show_exc"><span class="n"><a id="t319" href="#t319">319</a></span><span class="t"> <span class="nam">result</span> <span class="op">=</span> <span class="nam">rank_headlines</span><span class="op">(</span><span class="nam">test_headlines</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="exc show_exc"><span class="n"><a id="t320" href="#t320">320</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">"MUST_READ:"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="exc show_exc"><span class="n"><a id="t321" href="#t321">321</a></span><span class="t"> <span class="key">for</span> <span class="nam">h</span> <span class="key">in</span> <span class="nam">result</span><span class="op">[</span><span class="str">"must_read"</span><span class="op">]</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="exc show_exc"><span class="n"><a id="t322" href="#t322">322</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f" [{h['_score']:.2f}] {h['title']} ({h['source']})"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="exc show_exc"><span class="n"><a id="t323" href="#t323">323</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">"\nSCAN:"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="exc show_exc"><span class="n"><a id="t324" href="#t324">324</a></span><span class="t"> <span class="key">for</span> <span class="nam">h</span> <span class="key">in</span> <span class="nam">result</span><span class="op">[</span><span class="str">"scan"</span><span class="op">]</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="exc show_exc"><span class="n"><a id="t325" href="#t325">325</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f" [{h['_score']:.2f}] {h['title']} ({h['source']})"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
</main>
|
||||
<footer>
|
||||
<div class="content">
|
||||
<p>
|
||||
<a class="nav" href="z_de1a740d5dc98ffd_portfolio_py.html">« prev</a>
|
||||
<a class="nav" href="index.html">^ index</a>
|
||||
<a class="nav" href="z_de1a740d5dc98ffd_research_py.html">» next</a>
|
||||
|
||||
<a class="nav" href="https://coverage.readthedocs.io/en/7.13.2">coverage.py v7.13.2</a>,
|
||||
created at 2026-02-01 16:34 -0800
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
380
htmlcov/z_de1a740d5dc98ffd_research_py.html
generated
Normal file
380
htmlcov/z_de1a740d5dc98ffd_research_py.html
generated
Normal file
@@ -0,0 +1,380 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>Coverage for scripts/research.py: 65%</title>
|
||||
<link rel="icon" sizes="32x32" href="favicon_32_cb_c827f16f.png">
|
||||
<link rel="stylesheet" href="style_cb_9ff733b0.css" type="text/css">
|
||||
<script src="coverage_html_cb_dd2e7eb5.js" defer></script>
|
||||
</head>
|
||||
<body class="pyfile">
|
||||
<header>
|
||||
<div class="content">
|
||||
<h1>
|
||||
<span class="text">Coverage for </span><b>scripts / research.py</b>:
|
||||
<span class="pc_cov">65%</span>
|
||||
</h1>
|
||||
<aside id="help_panel_wrapper">
|
||||
<input id="help_panel_state" type="checkbox">
|
||||
<label for="help_panel_state">
|
||||
<img id="keyboard_icon" src="keybd_closed_cb_900cfef5.png" alt="Show/hide keyboard shortcuts">
|
||||
</label>
|
||||
<div id="help_panel">
|
||||
<p class="legend">Shortcuts on this page</p>
|
||||
<div class="keyhelp">
|
||||
<p>
|
||||
<kbd>r</kbd>
|
||||
<kbd>m</kbd>
|
||||
<kbd>x</kbd>
|
||||
toggle line displays
|
||||
</p>
|
||||
<p>
|
||||
<kbd>j</kbd>
|
||||
<kbd>k</kbd>
|
||||
next/prev highlighted chunk
|
||||
</p>
|
||||
<p>
|
||||
<kbd>0</kbd> (zero) top of page
|
||||
</p>
|
||||
<p>
|
||||
<kbd>1</kbd> (one) first highlighted chunk
|
||||
</p>
|
||||
<p>
|
||||
<kbd>[</kbd>
|
||||
<kbd>]</kbd>
|
||||
prev/next file
|
||||
</p>
|
||||
<p>
|
||||
<kbd>u</kbd> up to the index
|
||||
</p>
|
||||
<p>
|
||||
<kbd>?</kbd> show/hide this help
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
<h2>
|
||||
<span class="text">130 statements </span>
|
||||
<button type="button" class="run button_toggle_run" value="run" data-shortcut="r" title="Toggle lines run">85<span class="text"> run</span></button>
|
||||
<button type="button" class="mis show_mis button_toggle_mis" value="mis" data-shortcut="m" title="Toggle lines missing">45<span class="text"> missing</span></button>
|
||||
<button type="button" class="exc show_exc button_toggle_exc" value="exc" data-shortcut="x" title="Toggle lines excluded">2<span class="text"> excluded</span></button>
|
||||
</h2>
|
||||
<p class="text">
|
||||
<a id="prevFileLink" class="nav" href="z_de1a740d5dc98ffd_ranking_py.html">« prev</a>
|
||||
<a id="indexLink" class="nav" href="index.html">^ index</a>
|
||||
<a id="nextFileLink" class="nav" href="z_de1a740d5dc98ffd_setup_py.html">» next</a>
|
||||
|
||||
<a class="nav" href="https://coverage.readthedocs.io/en/7.13.2">coverage.py v7.13.2</a>,
|
||||
created at 2026-02-01 16:34 -0800
|
||||
</p>
|
||||
<aside class="hidden">
|
||||
<button type="button" class="button_next_chunk" data-shortcut="j"></button>
|
||||
<button type="button" class="button_prev_chunk" data-shortcut="k"></button>
|
||||
<button type="button" class="button_top_of_page" data-shortcut="0"></button>
|
||||
<button type="button" class="button_first_chunk" data-shortcut="1"></button>
|
||||
<button type="button" class="button_prev_file" data-shortcut="["></button>
|
||||
<button type="button" class="button_next_file" data-shortcut="]"></button>
|
||||
<button type="button" class="button_to_index" data-shortcut="u"></button>
|
||||
<button type="button" class="button_show_hide_help" data-shortcut="?"></button>
|
||||
</aside>
|
||||
</div>
|
||||
</header>
|
||||
<main id="source">
|
||||
<p class="pln"><span class="n"><a id="t1" href="#t1">1</a></span><span class="t"><span class="com">#!/usr/bin/env python3</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t2" href="#t2">2</a></span><span class="t"><span class="str">"""</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t3" href="#t3">3</a></span><span class="t"><span class="str">Research Module - Deep research using Gemini CLI.</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t4" href="#t4">4</a></span><span class="t"><span class="str">Crawls articles, finds correlations, researches companies.</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t5" href="#t5">5</a></span><span class="t"><span class="str">Outputs research_report.md for later analysis.</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t6" href="#t6">6</a></span><span class="t"><span class="str">"""</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t7" href="#t7">7</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t8" href="#t8">8</a></span><span class="t"><span class="key">import</span> <span class="nam">argparse</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t9" href="#t9">9</a></span><span class="t"><span class="key">import</span> <span class="nam">json</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t10" href="#t10">10</a></span><span class="t"><span class="key">import</span> <span class="nam">os</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t11" href="#t11">11</a></span><span class="t"><span class="key">import</span> <span class="nam">shutil</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t12" href="#t12">12</a></span><span class="t"><span class="key">import</span> <span class="nam">subprocess</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t13" href="#t13">13</a></span><span class="t"><span class="key">import</span> <span class="nam">sys</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t14" href="#t14">14</a></span><span class="t"><span class="key">from</span> <span class="nam">datetime</span> <span class="key">import</span> <span class="nam">datetime</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t15" href="#t15">15</a></span><span class="t"><span class="key">from</span> <span class="nam">pathlib</span> <span class="key">import</span> <span class="nam">Path</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t16" href="#t16">16</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t17" href="#t17">17</a></span><span class="t"><span class="key">from</span> <span class="nam">utils</span> <span class="key">import</span> <span class="nam">ensure_venv</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t18" href="#t18">18</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t19" href="#t19">19</a></span><span class="t"><span class="key">from</span> <span class="nam">fetch_news</span> <span class="key">import</span> <span class="nam">PortfolioError</span><span class="op">,</span> <span class="nam">get_market_news</span><span class="op">,</span> <span class="nam">get_portfolio_news</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t20" href="#t20">20</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t21" href="#t21">21</a></span><span class="t"><span class="nam">SCRIPT_DIR</span> <span class="op">=</span> <span class="nam">Path</span><span class="op">(</span><span class="nam">__file__</span><span class="op">)</span><span class="op">.</span><span class="nam">parent</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t22" href="#t22">22</a></span><span class="t"><span class="nam">CONFIG_DIR</span> <span class="op">=</span> <span class="nam">SCRIPT_DIR</span><span class="op">.</span><span class="nam">parent</span> <span class="op">/</span> <span class="str">"config"</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t23" href="#t23">23</a></span><span class="t"><span class="nam">OUTPUT_DIR</span> <span class="op">=</span> <span class="nam">SCRIPT_DIR</span><span class="op">.</span><span class="nam">parent</span> <span class="op">/</span> <span class="str">"research"</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t24" href="#t24">24</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t25" href="#t25">25</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t26" href="#t26">26</a></span><span class="t"><span class="nam">ensure_venv</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t27" href="#t27">27</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t28" href="#t28">28</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t29" href="#t29">29</a></span><span class="t"><span class="key">def</span> <span class="nam">format_market_data</span><span class="op">(</span><span class="nam">market_data</span><span class="op">:</span> <span class="nam">dict</span><span class="op">)</span> <span class="op">-></span> <span class="nam">str</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t30" href="#t30">30</a></span><span class="t"> <span class="str">"""Format market data for research prompt."""</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t31" href="#t31">31</a></span><span class="t"> <span class="nam">lines</span> <span class="op">=</span> <span class="op">[</span><span class="str">"## Market Data\n"</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t32" href="#t32">32</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t33" href="#t33">33</a></span><span class="t"> <span class="key">for</span> <span class="nam">region</span><span class="op">,</span> <span class="nam">data</span> <span class="key">in</span> <span class="nam">market_data</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'markets'</span><span class="op">,</span> <span class="op">{</span><span class="op">}</span><span class="op">)</span><span class="op">.</span><span class="nam">items</span><span class="op">(</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t34" href="#t34">34</a></span><span class="t"> <span class="nam">lines</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="str">f"### {data['name']}"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t35" href="#t35">35</a></span><span class="t"> <span class="key">for</span> <span class="nam">symbol</span><span class="op">,</span> <span class="nam">idx</span> <span class="key">in</span> <span class="nam">data</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'indices'</span><span class="op">,</span> <span class="op">{</span><span class="op">}</span><span class="op">)</span><span class="op">.</span><span class="nam">items</span><span class="op">(</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t36" href="#t36">36</a></span><span class="t"> <span class="key">if</span> <span class="str">'data'</span> <span class="key">in</span> <span class="nam">idx</span> <span class="key">and</span> <span class="nam">idx</span><span class="op">[</span><span class="str">'data'</span><span class="op">]</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t37" href="#t37">37</a></span><span class="t"> <span class="nam">price</span> <span class="op">=</span> <span class="nam">idx</span><span class="op">[</span><span class="str">'data'</span><span class="op">]</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'price'</span><span class="op">,</span> <span class="str">'N/A'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t38" href="#t38">38</a></span><span class="t"> <span class="nam">change_pct</span> <span class="op">=</span> <span class="nam">idx</span><span class="op">[</span><span class="str">'data'</span><span class="op">]</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'change_percent'</span><span class="op">,</span> <span class="num">0</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t39" href="#t39">39</a></span><span class="t"> <span class="nam">emoji</span> <span class="op">=</span> <span class="str">'📈'</span> <span class="key">if</span> <span class="nam">change_pct</span> <span class="op">>=</span> <span class="num">0</span> <span class="key">else</span> <span class="str">'📉'</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t40" href="#t40">40</a></span><span class="t"> <span class="nam">lines</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="str">f"- {idx['name']}: {price} ({change_pct:+.2f}%) {emoji}"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t41" href="#t41">41</a></span><span class="t"> <span class="nam">lines</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="str">""</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t42" href="#t42">42</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t43" href="#t43">43</a></span><span class="t"> <span class="key">return</span> <span class="str">'\n'</span><span class="op">.</span><span class="nam">join</span><span class="op">(</span><span class="nam">lines</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t44" href="#t44">44</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t45" href="#t45">45</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t46" href="#t46">46</a></span><span class="t"><span class="key">def</span> <span class="nam">format_headlines</span><span class="op">(</span><span class="nam">headlines</span><span class="op">:</span> <span class="nam">list</span><span class="op">)</span> <span class="op">-></span> <span class="nam">str</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t47" href="#t47">47</a></span><span class="t"> <span class="str">"""Format headlines for research prompt."""</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t48" href="#t48">48</a></span><span class="t"> <span class="nam">lines</span> <span class="op">=</span> <span class="op">[</span><span class="str">"## Current Headlines\n"</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t49" href="#t49">49</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t50" href="#t50">50</a></span><span class="t"> <span class="key">for</span> <span class="nam">article</span> <span class="key">in</span> <span class="nam">headlines</span><span class="op">[</span><span class="op">:</span><span class="num">20</span><span class="op">]</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t51" href="#t51">51</a></span><span class="t"> <span class="nam">source</span> <span class="op">=</span> <span class="nam">article</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'source'</span><span class="op">,</span> <span class="str">'Unknown'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t52" href="#t52">52</a></span><span class="t"> <span class="nam">title</span> <span class="op">=</span> <span class="nam">article</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'title'</span><span class="op">,</span> <span class="str">''</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t53" href="#t53">53</a></span><span class="t"> <span class="nam">link</span> <span class="op">=</span> <span class="nam">article</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'link'</span><span class="op">,</span> <span class="str">''</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t54" href="#t54">54</a></span><span class="t"> <span class="nam">lines</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="str">f"- [{source}] {title}"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t55" href="#t55">55</a></span><span class="t"> <span class="key">if</span> <span class="nam">link</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t56" href="#t56">56</a></span><span class="t"> <span class="nam">lines</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="str">f" URL: {link}"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t57" href="#t57">57</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t58" href="#t58">58</a></span><span class="t"> <span class="key">return</span> <span class="str">'\n'</span><span class="op">.</span><span class="nam">join</span><span class="op">(</span><span class="nam">lines</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t59" href="#t59">59</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t60" href="#t60">60</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t61" href="#t61">61</a></span><span class="t"><span class="key">def</span> <span class="nam">format_portfolio_news</span><span class="op">(</span><span class="nam">portfolio_data</span><span class="op">:</span> <span class="nam">dict</span><span class="op">)</span> <span class="op">-></span> <span class="nam">str</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t62" href="#t62">62</a></span><span class="t"> <span class="str">"""Format portfolio news for research prompt."""</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t63" href="#t63">63</a></span><span class="t"> <span class="nam">lines</span> <span class="op">=</span> <span class="op">[</span><span class="str">"## Portfolio Analysis\n"</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t64" href="#t64">64</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t65" href="#t65">65</a></span><span class="t"> <span class="key">for</span> <span class="nam">symbol</span><span class="op">,</span> <span class="nam">data</span> <span class="key">in</span> <span class="nam">portfolio_data</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'stocks'</span><span class="op">,</span> <span class="op">{</span><span class="op">}</span><span class="op">)</span><span class="op">.</span><span class="nam">items</span><span class="op">(</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t66" href="#t66">66</a></span><span class="t"> <span class="nam">quote</span> <span class="op">=</span> <span class="nam">data</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'quote'</span><span class="op">,</span> <span class="op">{</span><span class="op">}</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t67" href="#t67">67</a></span><span class="t"> <span class="nam">price</span> <span class="op">=</span> <span class="nam">quote</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'price'</span><span class="op">,</span> <span class="str">'N/A'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t68" href="#t68">68</a></span><span class="t"> <span class="nam">change_pct</span> <span class="op">=</span> <span class="nam">quote</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'change_percent'</span><span class="op">,</span> <span class="num">0</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t69" href="#t69">69</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t70" href="#t70">70</a></span><span class="t"> <span class="nam">lines</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="str">f"### {symbol} (${price}, {change_pct:+.2f}%)"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t71" href="#t71">71</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t72" href="#t72">72</a></span><span class="t"> <span class="key">for</span> <span class="nam">article</span> <span class="key">in</span> <span class="nam">data</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'articles'</span><span class="op">,</span> <span class="op">[</span><span class="op">]</span><span class="op">)</span><span class="op">[</span><span class="op">:</span><span class="num">5</span><span class="op">]</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t73" href="#t73">73</a></span><span class="t"> <span class="nam">title</span> <span class="op">=</span> <span class="nam">article</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'title'</span><span class="op">,</span> <span class="str">''</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t74" href="#t74">74</a></span><span class="t"> <span class="nam">link</span> <span class="op">=</span> <span class="nam">article</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'link'</span><span class="op">,</span> <span class="str">''</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t75" href="#t75">75</a></span><span class="t"> <span class="nam">lines</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="str">f"- {title}"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t76" href="#t76">76</a></span><span class="t"> <span class="key">if</span> <span class="nam">link</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t77" href="#t77">77</a></span><span class="t"> <span class="nam">lines</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="str">f" URL: {link}"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t78" href="#t78">78</a></span><span class="t"> <span class="nam">lines</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="str">""</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t79" href="#t79">79</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t80" href="#t80">80</a></span><span class="t"> <span class="key">return</span> <span class="str">'\n'</span><span class="op">.</span><span class="nam">join</span><span class="op">(</span><span class="nam">lines</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t81" href="#t81">81</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t82" href="#t82">82</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t83" href="#t83">83</a></span><span class="t"><span class="key">def</span> <span class="nam">gemini_available</span><span class="op">(</span><span class="op">)</span> <span class="op">-></span> <span class="nam">bool</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t84" href="#t84">84</a></span><span class="t"> <span class="key">return</span> <span class="nam">shutil</span><span class="op">.</span><span class="nam">which</span><span class="op">(</span><span class="str">'gemini'</span><span class="op">)</span> <span class="key">is</span> <span class="key">not</span> <span class="key">None</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t85" href="#t85">85</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t86" href="#t86">86</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t87" href="#t87">87</a></span><span class="t"><span class="key">def</span> <span class="nam">research_with_gemini</span><span class="op">(</span><span class="nam">content</span><span class="op">:</span> <span class="nam">str</span><span class="op">,</span> <span class="nam">focus_areas</span><span class="op">:</span> <span class="nam">list</span> <span class="op">=</span> <span class="key">None</span><span class="op">)</span> <span class="op">-></span> <span class="nam">str</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t88" href="#t88">88</a></span><span class="t"> <span class="str">"""Perform deep research using Gemini CLI.</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t89" href="#t89">89</a></span><span class="t"><span class="str"> </span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t90" href="#t90">90</a></span><span class="t"><span class="str"> Args:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t91" href="#t91">91</a></span><span class="t"><span class="str"> content: Combined market/headlines/portfolio content</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t92" href="#t92">92</a></span><span class="t"><span class="str"> focus_areas: Optional list of focus areas (e.g., ['earnings', 'macro', 'sectors'])</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t93" href="#t93">93</a></span><span class="t"><span class="str"> </span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t94" href="#t94">94</a></span><span class="t"><span class="str"> Returns:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t95" href="#t95">95</a></span><span class="t"><span class="str"> Research report text</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t96" href="#t96">96</a></span><span class="t"><span class="str"> """</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t97" href="#t97">97</a></span><span class="t"> <span class="nam">focus_prompt</span> <span class="op">=</span> <span class="str">""</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t98" href="#t98">98</a></span><span class="t"> <span class="key">if</span> <span class="nam">focus_areas</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t99" href="#t99">99</a></span><span class="t"> <span class="nam">focus_prompt</span> <span class="op">=</span> <span class="str">f"""</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t100" href="#t100">100</a></span><span class="t"><span class="str">Focus areas for the research:</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t101" href="#t101">101</a></span><span class="t"><span class="str">{', '.join(f'- {area}' for area in focus_areas)}</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t102" href="#t102">102</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t103" href="#t103">103</a></span><span class="t"><span class="str">Go deep on each area.</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t104" href="#t104">104</a></span><span class="t"><span class="str">"""</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t105" href="#t105">105</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t106" href="#t106">106</a></span><span class="t"> <span class="nam">prompt</span> <span class="op">=</span> <span class="str">f"""You are an experienced investment research analyst.</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t107" href="#t107">107</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t108" href="#t108">108</a></span><span class="t"><span class="str">Your task is to deliver deep research on current market developments.</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t109" href="#t109">109</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t110" href="#t110">110</a></span><span class="t"><span class="str">{focus_prompt}</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t111" href="#t111">111</a></span><span class="t"><span class="str">Please analyze the following market data:</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t112" href="#t112">112</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t113" href="#t113">113</a></span><span class="t"><span class="str">{content}</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t114" href="#t114">114</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t115" href="#t115">115</a></span><span class="t"><span class="str">## Analysis Requirements:</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t116" href="#t116">116</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t117" href="#t117">117</a></span><span class="t"><span class="str">1. **Macro Trends**: What is driving the market today? Which economic data/decisions matter?</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t118" href="#t118">118</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t119" href="#t119">119</a></span><span class="t"><span class="str">2. **Sector Analysis**: Which sectors are performing best/worst? Why?</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t120" href="#t120">120</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t121" href="#t121">121</a></span><span class="t"><span class="str">3. **Company News**: Relevant earnings, M&A, product launches?</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t122" href="#t122">122</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t123" href="#t123">123</a></span><span class="t"><span class="str">4. **Risks**: What downside risks should be noted?</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t124" href="#t124">124</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t125" href="#t125">125</a></span><span class="t"><span class="str">5. **Opportunities**: Which positive developments offer opportunities?</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t126" href="#t126">126</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t127" href="#t127">127</a></span><span class="t"><span class="str">6. **Correlations**: Are there links between different news items/asset classes?</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t128" href="#t128">128</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t129" href="#t129">129</a></span><span class="t"><span class="str">7. **Trade Ideas**: Concrete setups based on the analysis (not financial advice!)</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t130" href="#t130">130</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t131" href="#t131">131</a></span><span class="t"><span class="str">8. **Sources**: Original links for further research</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t132" href="#t132">132</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t133" href="#t133">133</a></span><span class="t"><span class="str">Be analytical, objective, and opinionated where appropriate.</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t134" href="#t134">134</a></span><span class="t"><span class="str">Deliver a substantial report (500-800 words).</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t135" href="#t135">135</a></span><span class="t"><span class="str">"""</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t136" href="#t136">136</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t137" href="#t137">137</a></span><span class="t"> <span class="key">try</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t138" href="#t138">138</a></span><span class="t"> <span class="nam">result</span> <span class="op">=</span> <span class="nam">subprocess</span><span class="op">.</span><span class="nam">run</span><span class="op">(</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t139" href="#t139">139</a></span><span class="t"> <span class="op">[</span><span class="str">'gemini'</span><span class="op">,</span> <span class="nam">prompt</span><span class="op">]</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t140" href="#t140">140</a></span><span class="t"> <span class="nam">capture_output</span><span class="op">=</span><span class="key">True</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t141" href="#t141">141</a></span><span class="t"> <span class="nam">text</span><span class="op">=</span><span class="key">True</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t142" href="#t142">142</a></span><span class="t"> <span class="nam">timeout</span><span class="op">=</span><span class="num">120</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t143" href="#t143">143</a></span><span class="t"> <span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t144" href="#t144">144</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t145" href="#t145">145</a></span><span class="t"> <span class="key">if</span> <span class="nam">result</span><span class="op">.</span><span class="nam">returncode</span> <span class="op">==</span> <span class="num">0</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t146" href="#t146">146</a></span><span class="t"> <span class="key">return</span> <span class="nam">result</span><span class="op">.</span><span class="nam">stdout</span><span class="op">.</span><span class="nam">strip</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t147" href="#t147">147</a></span><span class="t"> <span class="key">else</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t148" href="#t148">148</a></span><span class="t"> <span class="key">return</span> <span class="str">f"⚠️ Gemini research error: {result.stderr}"</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t149" href="#t149">149</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t150" href="#t150">150</a></span><span class="t"> <span class="key">except</span> <span class="nam">subprocess</span><span class="op">.</span><span class="nam">TimeoutExpired</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t151" href="#t151">151</a></span><span class="t"> <span class="key">return</span> <span class="str">"⚠️ Gemini research timeout"</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t152" href="#t152">152</a></span><span class="t"> <span class="key">except</span> <span class="nam">FileNotFoundError</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t153" href="#t153">153</a></span><span class="t"> <span class="key">return</span> <span class="str">"⚠️ Gemini CLI not found. Install: brew install gemini-cli"</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t154" href="#t154">154</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t155" href="#t155">155</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t156" href="#t156">156</a></span><span class="t"><span class="key">def</span> <span class="nam">format_raw_data_report</span><span class="op">(</span><span class="nam">market_data</span><span class="op">:</span> <span class="nam">dict</span><span class="op">,</span> <span class="nam">portfolio_data</span><span class="op">:</span> <span class="nam">dict</span><span class="op">)</span> <span class="op">-></span> <span class="nam">str</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t157" href="#t157">157</a></span><span class="t"> <span class="nam">parts</span> <span class="op">=</span> <span class="op">[</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t158" href="#t158">158</a></span><span class="t"> <span class="key">if</span> <span class="nam">market_data</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t159" href="#t159">159</a></span><span class="t"> <span class="nam">parts</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="nam">format_market_data</span><span class="op">(</span><span class="nam">market_data</span><span class="op">)</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t160" href="#t160">160</a></span><span class="t"> <span class="key">if</span> <span class="nam">market_data</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'headlines'</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t161" href="#t161">161</a></span><span class="t"> <span class="nam">parts</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="nam">format_headlines</span><span class="op">(</span><span class="nam">market_data</span><span class="op">[</span><span class="str">'headlines'</span><span class="op">]</span><span class="op">)</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t162" href="#t162">162</a></span><span class="t"> <span class="key">if</span> <span class="nam">portfolio_data</span> <span class="key">and</span> <span class="str">'error'</span> <span class="key">not</span> <span class="key">in</span> <span class="nam">portfolio_data</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t163" href="#t163">163</a></span><span class="t"> <span class="nam">parts</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="nam">format_portfolio_news</span><span class="op">(</span><span class="nam">portfolio_data</span><span class="op">)</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t164" href="#t164">164</a></span><span class="t"> <span class="key">return</span> <span class="str">'\n\n'</span><span class="op">.</span><span class="nam">join</span><span class="op">(</span><span class="nam">parts</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t165" href="#t165">165</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t166" href="#t166">166</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t167" href="#t167">167</a></span><span class="t"><span class="key">def</span> <span class="nam">generate_research_content</span><span class="op">(</span><span class="nam">market_data</span><span class="op">:</span> <span class="nam">dict</span><span class="op">,</span> <span class="nam">portfolio_data</span><span class="op">:</span> <span class="nam">dict</span><span class="op">,</span> <span class="nam">focus_areas</span><span class="op">:</span> <span class="nam">list</span> <span class="op">=</span> <span class="key">None</span><span class="op">)</span> <span class="op">-></span> <span class="nam">dict</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t168" href="#t168">168</a></span><span class="t"> <span class="nam">raw_report</span> <span class="op">=</span> <span class="nam">format_raw_data_report</span><span class="op">(</span><span class="nam">market_data</span><span class="op">,</span> <span class="nam">portfolio_data</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t169" href="#t169">169</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">raw_report</span><span class="op">.</span><span class="nam">strip</span><span class="op">(</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t170" href="#t170">170</a></span><span class="t"> <span class="key">return</span> <span class="op">{</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t171" href="#t171">171</a></span><span class="t"> <span class="str">'report'</span><span class="op">:</span> <span class="str">''</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t172" href="#t172">172</a></span><span class="t"> <span class="str">'source'</span><span class="op">:</span> <span class="str">'none'</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t173" href="#t173">173</a></span><span class="t"> <span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t174" href="#t174">174</a></span><span class="t"> <span class="key">if</span> <span class="nam">gemini_available</span><span class="op">(</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t175" href="#t175">175</a></span><span class="t"> <span class="key">return</span> <span class="op">{</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t176" href="#t176">176</a></span><span class="t"> <span class="str">'report'</span><span class="op">:</span> <span class="nam">research_with_gemini</span><span class="op">(</span><span class="nam">raw_report</span><span class="op">,</span> <span class="nam">focus_areas</span><span class="op">)</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t177" href="#t177">177</a></span><span class="t"> <span class="str">'source'</span><span class="op">:</span> <span class="str">'gemini'</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t178" href="#t178">178</a></span><span class="t"> <span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t179" href="#t179">179</a></span><span class="t"> <span class="key">return</span> <span class="op">{</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t180" href="#t180">180</a></span><span class="t"> <span class="str">'report'</span><span class="op">:</span> <span class="nam">raw_report</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t181" href="#t181">181</a></span><span class="t"> <span class="str">'source'</span><span class="op">:</span> <span class="str">'raw'</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t182" href="#t182">182</a></span><span class="t"> <span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t183" href="#t183">183</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t184" href="#t184">184</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t185" href="#t185">185</a></span><span class="t"><span class="key">def</span> <span class="nam">generate_research_report</span><span class="op">(</span><span class="nam">args</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t186" href="#t186">186</a></span><span class="t"> <span class="str">"""Generate full research report."""</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t187" href="#t187">187</a></span><span class="t"> <span class="nam">OUTPUT_DIR</span><span class="op">.</span><span class="nam">mkdir</span><span class="op">(</span><span class="nam">parents</span><span class="op">=</span><span class="key">True</span><span class="op">,</span> <span class="nam">exist_ok</span><span class="op">=</span><span class="key">True</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t188" href="#t188">188</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t189" href="#t189">189</a></span><span class="t"> <span class="nam">config_path</span> <span class="op">=</span> <span class="nam">CONFIG_DIR</span> <span class="op">/</span> <span class="str">"config.json"</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t190" href="#t190">190</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">config_path</span><span class="op">.</span><span class="nam">exists</span><span class="op">(</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t191" href="#t191">191</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">"⚠️ No config found. Run 'finance-news wizard' first."</span><span class="op">,</span> <span class="nam">file</span><span class="op">=</span><span class="nam">sys</span><span class="op">.</span><span class="nam">stderr</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t192" href="#t192">192</a></span><span class="t"> <span class="nam">sys</span><span class="op">.</span><span class="nam">exit</span><span class="op">(</span><span class="num">1</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t193" href="#t193">193</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t194" href="#t194">194</a></span><span class="t"> <span class="com"># Fetch fresh data</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t195" href="#t195">195</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">"📡 Fetching market data..."</span><span class="op">,</span> <span class="nam">file</span><span class="op">=</span><span class="nam">sys</span><span class="op">.</span><span class="nam">stderr</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t196" href="#t196">196</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t197" href="#t197">197</a></span><span class="t"> <span class="com"># Get market overview</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t198" href="#t198">198</a></span><span class="t"> <span class="nam">market_data</span> <span class="op">=</span> <span class="nam">get_market_news</span><span class="op">(</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t199" href="#t199">199</a></span><span class="t"> <span class="nam">args</span><span class="op">.</span><span class="nam">limit</span> <span class="key">if</span> <span class="nam">hasattr</span><span class="op">(</span><span class="nam">args</span><span class="op">,</span> <span class="str">'limit'</span><span class="op">)</span> <span class="key">else</span> <span class="num">5</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t200" href="#t200">200</a></span><span class="t"> <span class="nam">regions</span><span class="op">=</span><span class="nam">args</span><span class="op">.</span><span class="nam">regions</span><span class="op">.</span><span class="nam">split</span><span class="op">(</span><span class="str">','</span><span class="op">)</span> <span class="key">if</span> <span class="nam">hasattr</span><span class="op">(</span><span class="nam">args</span><span class="op">,</span> <span class="str">'regions'</span><span class="op">)</span> <span class="key">else</span> <span class="op">[</span><span class="str">"us"</span><span class="op">,</span> <span class="str">"europe"</span><span class="op">]</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t201" href="#t201">201</a></span><span class="t"> <span class="nam">max_indices_per_region</span><span class="op">=</span><span class="num">2</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t202" href="#t202">202</a></span><span class="t"> <span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t203" href="#t203">203</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t204" href="#t204">204</a></span><span class="t"> <span class="com"># Get portfolio news</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t205" href="#t205">205</a></span><span class="t"> <span class="key">try</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t206" href="#t206">206</a></span><span class="t"> <span class="nam">portfolio_data</span> <span class="op">=</span> <span class="nam">get_portfolio_news</span><span class="op">(</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t207" href="#t207">207</a></span><span class="t"> <span class="nam">args</span><span class="op">.</span><span class="nam">limit</span> <span class="key">if</span> <span class="nam">hasattr</span><span class="op">(</span><span class="nam">args</span><span class="op">,</span> <span class="str">'limit'</span><span class="op">)</span> <span class="key">else</span> <span class="num">5</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t208" href="#t208">208</a></span><span class="t"> <span class="nam">args</span><span class="op">.</span><span class="nam">max_stocks</span> <span class="key">if</span> <span class="nam">hasattr</span><span class="op">(</span><span class="nam">args</span><span class="op">,</span> <span class="str">'max_stocks'</span><span class="op">)</span> <span class="key">else</span> <span class="num">10</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t209" href="#t209">209</a></span><span class="t"> <span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t210" href="#t210">210</a></span><span class="t"> <span class="key">except</span> <span class="nam">PortfolioError</span> <span class="key">as</span> <span class="nam">exc</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t211" href="#t211">211</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"⚠️ Skipping portfolio: {exc}"</span><span class="op">,</span> <span class="nam">file</span><span class="op">=</span><span class="nam">sys</span><span class="op">.</span><span class="nam">stderr</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t212" href="#t212">212</a></span><span class="t"> <span class="nam">portfolio_data</span> <span class="op">=</span> <span class="key">None</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t213" href="#t213">213</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t214" href="#t214">214</a></span><span class="t"> <span class="com"># Build report</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t215" href="#t215">215</a></span><span class="t"> <span class="nam">focus_areas</span> <span class="op">=</span> <span class="key">None</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t216" href="#t216">216</a></span><span class="t"> <span class="key">if</span> <span class="nam">hasattr</span><span class="op">(</span><span class="nam">args</span><span class="op">,</span> <span class="str">'focus'</span><span class="op">)</span> <span class="key">and</span> <span class="nam">args</span><span class="op">.</span><span class="nam">focus</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t217" href="#t217">217</a></span><span class="t"> <span class="nam">focus_areas</span> <span class="op">=</span> <span class="nam">args</span><span class="op">.</span><span class="nam">focus</span><span class="op">.</span><span class="nam">split</span><span class="op">(</span><span class="str">','</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t218" href="#t218">218</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t219" href="#t219">219</a></span><span class="t"> <span class="nam">research_result</span> <span class="op">=</span> <span class="nam">generate_research_content</span><span class="op">(</span><span class="nam">market_data</span><span class="op">,</span> <span class="nam">portfolio_data</span><span class="op">,</span> <span class="nam">focus_areas</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t220" href="#t220">220</a></span><span class="t"> <span class="nam">research_report</span> <span class="op">=</span> <span class="nam">research_result</span><span class="op">[</span><span class="str">'report'</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t221" href="#t221">221</a></span><span class="t"> <span class="nam">source</span> <span class="op">=</span> <span class="nam">research_result</span><span class="op">[</span><span class="str">'source'</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t222" href="#t222">222</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t223" href="#t223">223</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">research_report</span><span class="op">.</span><span class="nam">strip</span><span class="op">(</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t224" href="#t224">224</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">"⚠️ No data available for research"</span><span class="op">,</span> <span class="nam">file</span><span class="op">=</span><span class="nam">sys</span><span class="op">.</span><span class="nam">stderr</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t225" href="#t225">225</a></span><span class="t"> <span class="key">return</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t226" href="#t226">226</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t227" href="#t227">227</a></span><span class="t"> <span class="key">if</span> <span class="nam">source</span> <span class="op">==</span> <span class="str">'gemini'</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t228" href="#t228">228</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">"🔬 Running deep research with Gemini..."</span><span class="op">,</span> <span class="nam">file</span><span class="op">=</span><span class="nam">sys</span><span class="op">.</span><span class="nam">stderr</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t229" href="#t229">229</a></span><span class="t"> <span class="key">else</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t230" href="#t230">230</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">"🧾 Gemini not available; using raw data report"</span><span class="op">,</span> <span class="nam">file</span><span class="op">=</span><span class="nam">sys</span><span class="op">.</span><span class="nam">stderr</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t231" href="#t231">231</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t232" href="#t232">232</a></span><span class="t"> <span class="com"># Add metadata header</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t233" href="#t233">233</a></span><span class="t"> <span class="nam">timestamp</span> <span class="op">=</span> <span class="nam">datetime</span><span class="op">.</span><span class="nam">now</span><span class="op">(</span><span class="op">)</span><span class="op">.</span><span class="nam">isoformat</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t234" href="#t234">234</a></span><span class="t"> <span class="nam">date_str</span> <span class="op">=</span> <span class="nam">datetime</span><span class="op">.</span><span class="nam">now</span><span class="op">(</span><span class="op">)</span><span class="op">.</span><span class="nam">strftime</span><span class="op">(</span><span class="str">"%Y-%m-%d %H:%M"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t235" href="#t235">235</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t236" href="#t236">236</a></span><span class="t"> <span class="nam">full_report</span> <span class="op">=</span> <span class="str">f"""# Market Research Report</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t237" href="#t237">237</a></span><span class="t"><span class="str">**Generiert:** {date_str}</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t238" href="#t238">238</a></span><span class="t"><span class="str">**Quelle:** Finance News Skill</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t239" href="#t239">239</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t240" href="#t240">240</a></span><span class="t"><span class="str">---</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t241" href="#t241">241</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t242" href="#t242">242</a></span><span class="t"><span class="str">{research_report}</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t243" href="#t243">243</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t244" href="#t244">244</a></span><span class="t"><span class="str">---</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t245" href="#t245">245</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t246" href="#t246">246</a></span><span class="t"><span class="str">*This report was generated automatically. Not financial advice.*</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t247" href="#t247">247</a></span><span class="t"><span class="str">"""</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t248" href="#t248">248</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t249" href="#t249">249</a></span><span class="t"> <span class="com"># Save to file</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t250" href="#t250">250</a></span><span class="t"> <span class="nam">output_file</span> <span class="op">=</span> <span class="nam">OUTPUT_DIR</span> <span class="op">/</span> <span class="str">f"research_{datetime.now().strftime('%Y-%m-%d')}.md"</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t251" href="#t251">251</a></span><span class="t"> <span class="key">with</span> <span class="nam">open</span><span class="op">(</span><span class="nam">output_file</span><span class="op">,</span> <span class="str">'w'</span><span class="op">)</span> <span class="key">as</span> <span class="nam">f</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t252" href="#t252">252</a></span><span class="t"> <span class="nam">f</span><span class="op">.</span><span class="nam">write</span><span class="op">(</span><span class="nam">full_report</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t253" href="#t253">253</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t254" href="#t254">254</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"✅ Research report saved to: {output_file}"</span><span class="op">,</span> <span class="nam">file</span><span class="op">=</span><span class="nam">sys</span><span class="op">.</span><span class="nam">stderr</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t255" href="#t255">255</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t256" href="#t256">256</a></span><span class="t"> <span class="com"># Also output to stdout</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t257" href="#t257">257</a></span><span class="t"> <span class="key">if</span> <span class="nam">args</span><span class="op">.</span><span class="nam">json</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t258" href="#t258">258</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="nam">json</span><span class="op">.</span><span class="nam">dumps</span><span class="op">(</span><span class="op">{</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t259" href="#t259">259</a></span><span class="t"> <span class="str">'report'</span><span class="op">:</span> <span class="nam">research_report</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t260" href="#t260">260</a></span><span class="t"> <span class="str">'saved_to'</span><span class="op">:</span> <span class="nam">str</span><span class="op">(</span><span class="nam">output_file</span><span class="op">)</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t261" href="#t261">261</a></span><span class="t"> <span class="str">'timestamp'</span><span class="op">:</span> <span class="nam">timestamp</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t262" href="#t262">262</a></span><span class="t"> <span class="op">}</span><span class="op">)</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t263" href="#t263">263</a></span><span class="t"> <span class="key">else</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t264" href="#t264">264</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">"\n"</span> <span class="op">+</span> <span class="str">"="</span><span class="op">*</span><span class="num">60</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t265" href="#t265">265</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">"RESEARCH REPORT"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t266" href="#t266">266</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">"="</span><span class="op">*</span><span class="num">60</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t267" href="#t267">267</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="nam">research_report</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t268" href="#t268">268</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t269" href="#t269">269</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t270" href="#t270">270</a></span><span class="t"><span class="key">def</span> <span class="nam">main</span><span class="op">(</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t271" href="#t271">271</a></span><span class="t"> <span class="nam">parser</span> <span class="op">=</span> <span class="nam">argparse</span><span class="op">.</span><span class="nam">ArgumentParser</span><span class="op">(</span><span class="nam">description</span><span class="op">=</span><span class="str">'Deep Market Research'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t272" href="#t272">272</a></span><span class="t"> <span class="nam">parser</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">'--limit'</span><span class="op">,</span> <span class="nam">type</span><span class="op">=</span><span class="nam">int</span><span class="op">,</span> <span class="nam">default</span><span class="op">=</span><span class="num">5</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">'Max headlines per source'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t273" href="#t273">273</a></span><span class="t"> <span class="nam">parser</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">'--regions'</span><span class="op">,</span> <span class="nam">default</span><span class="op">=</span><span class="str">'us,europe'</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">'Comma-separated regions'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t274" href="#t274">274</a></span><span class="t"> <span class="nam">parser</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">'--max-stocks'</span><span class="op">,</span> <span class="nam">type</span><span class="op">=</span><span class="nam">int</span><span class="op">,</span> <span class="nam">default</span><span class="op">=</span><span class="num">10</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">'Max portfolio stocks'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t275" href="#t275">275</a></span><span class="t"> <span class="nam">parser</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">'--focus'</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">'Focus areas (comma-separated)'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t276" href="#t276">276</a></span><span class="t"> <span class="nam">parser</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">'--json'</span><span class="op">,</span> <span class="nam">action</span><span class="op">=</span><span class="str">'store_true'</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">'Output as JSON'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t277" href="#t277">277</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t278" href="#t278">278</a></span><span class="t"> <span class="nam">args</span> <span class="op">=</span> <span class="nam">parser</span><span class="op">.</span><span class="nam">parse_args</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t279" href="#t279">279</a></span><span class="t"> <span class="nam">generate_research_report</span><span class="op">(</span><span class="nam">args</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t280" href="#t280">280</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t281" href="#t281">281</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="exc show_exc"><span class="n"><a id="t282" href="#t282">282</a></span><span class="t"><span class="key">if</span> <span class="nam">__name__</span> <span class="op">==</span> <span class="str">'__main__'</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="exc show_exc"><span class="n"><a id="t283" href="#t283">283</a></span><span class="t"> <span class="nam">main</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
</main>
|
||||
<footer>
|
||||
<div class="content">
|
||||
<p>
|
||||
<a class="nav" href="z_de1a740d5dc98ffd_ranking_py.html">« prev</a>
|
||||
<a class="nav" href="index.html">^ index</a>
|
||||
<a class="nav" href="z_de1a740d5dc98ffd_setup_py.html">» next</a>
|
||||
|
||||
<a class="nav" href="https://coverage.readthedocs.io/en/7.13.2">coverage.py v7.13.2</a>,
|
||||
created at 2026-02-01 16:34 -0800
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
387
htmlcov/z_de1a740d5dc98ffd_setup_py.html
generated
Normal file
387
htmlcov/z_de1a740d5dc98ffd_setup_py.html
generated
Normal file
@@ -0,0 +1,387 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>Coverage for scripts/setup.py: 26%</title>
|
||||
<link rel="icon" sizes="32x32" href="favicon_32_cb_c827f16f.png">
|
||||
<link rel="stylesheet" href="style_cb_9ff733b0.css" type="text/css">
|
||||
<script src="coverage_html_cb_dd2e7eb5.js" defer></script>
|
||||
</head>
|
||||
<body class="pyfile">
|
||||
<header>
|
||||
<div class="content">
|
||||
<h1>
|
||||
<span class="text">Coverage for </span><b>scripts / setup.py</b>:
|
||||
<span class="pc_cov">26%</span>
|
||||
</h1>
|
||||
<aside id="help_panel_wrapper">
|
||||
<input id="help_panel_state" type="checkbox">
|
||||
<label for="help_panel_state">
|
||||
<img id="keyboard_icon" src="keybd_closed_cb_900cfef5.png" alt="Show/hide keyboard shortcuts">
|
||||
</label>
|
||||
<div id="help_panel">
|
||||
<p class="legend">Shortcuts on this page</p>
|
||||
<div class="keyhelp">
|
||||
<p>
|
||||
<kbd>r</kbd>
|
||||
<kbd>m</kbd>
|
||||
<kbd>x</kbd>
|
||||
toggle line displays
|
||||
</p>
|
||||
<p>
|
||||
<kbd>j</kbd>
|
||||
<kbd>k</kbd>
|
||||
next/prev highlighted chunk
|
||||
</p>
|
||||
<p>
|
||||
<kbd>0</kbd> (zero) top of page
|
||||
</p>
|
||||
<p>
|
||||
<kbd>1</kbd> (one) first highlighted chunk
|
||||
</p>
|
||||
<p>
|
||||
<kbd>[</kbd>
|
||||
<kbd>]</kbd>
|
||||
prev/next file
|
||||
</p>
|
||||
<p>
|
||||
<kbd>u</kbd> up to the index
|
||||
</p>
|
||||
<p>
|
||||
<kbd>?</kbd> show/hide this help
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
<h2>
|
||||
<span class="text">168 statements </span>
|
||||
<button type="button" class="run button_toggle_run" value="run" data-shortcut="r" title="Toggle lines run">44<span class="text"> run</span></button>
|
||||
<button type="button" class="mis show_mis button_toggle_mis" value="mis" data-shortcut="m" title="Toggle lines missing">124<span class="text"> missing</span></button>
|
||||
<button type="button" class="exc show_exc button_toggle_exc" value="exc" data-shortcut="x" title="Toggle lines excluded">2<span class="text"> excluded</span></button>
|
||||
</h2>
|
||||
<p class="text">
|
||||
<a id="prevFileLink" class="nav" href="z_de1a740d5dc98ffd_research_py.html">« prev</a>
|
||||
<a id="indexLink" class="nav" href="index.html">^ index</a>
|
||||
<a id="nextFileLink" class="nav" href="z_de1a740d5dc98ffd_stocks_py.html">» next</a>
|
||||
|
||||
<a class="nav" href="https://coverage.readthedocs.io/en/7.13.2">coverage.py v7.13.2</a>,
|
||||
created at 2026-02-01 16:34 -0800
|
||||
</p>
|
||||
<aside class="hidden">
|
||||
<button type="button" class="button_next_chunk" data-shortcut="j"></button>
|
||||
<button type="button" class="button_prev_chunk" data-shortcut="k"></button>
|
||||
<button type="button" class="button_top_of_page" data-shortcut="0"></button>
|
||||
<button type="button" class="button_first_chunk" data-shortcut="1"></button>
|
||||
<button type="button" class="button_prev_file" data-shortcut="["></button>
|
||||
<button type="button" class="button_next_file" data-shortcut="]"></button>
|
||||
<button type="button" class="button_to_index" data-shortcut="u"></button>
|
||||
<button type="button" class="button_show_hide_help" data-shortcut="?"></button>
|
||||
</aside>
|
||||
</div>
|
||||
</header>
|
||||
<main id="source">
|
||||
<p class="pln"><span class="n"><a id="t1" href="#t1">1</a></span><span class="t"><span class="com">#!/usr/bin/env python3</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t2" href="#t2">2</a></span><span class="t"><span class="str">"""</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t3" href="#t3">3</a></span><span class="t"><span class="str">Finance News Skill - Interactive Setup</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t4" href="#t4">4</a></span><span class="t"><span class="str">Configures RSS feeds, WhatsApp channels, and cron jobs.</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t5" href="#t5">5</a></span><span class="t"><span class="str">"""</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t6" href="#t6">6</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t7" href="#t7">7</a></span><span class="t"><span class="key">import</span> <span class="nam">argparse</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t8" href="#t8">8</a></span><span class="t"><span class="key">import</span> <span class="nam">json</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t9" href="#t9">9</a></span><span class="t"><span class="key">import</span> <span class="nam">subprocess</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t10" href="#t10">10</a></span><span class="t"><span class="key">import</span> <span class="nam">sys</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t11" href="#t11">11</a></span><span class="t"><span class="key">from</span> <span class="nam">pathlib</span> <span class="key">import</span> <span class="nam">Path</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t12" href="#t12">12</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t13" href="#t13">13</a></span><span class="t"><span class="nam">SCRIPT_DIR</span> <span class="op">=</span> <span class="nam">Path</span><span class="op">(</span><span class="nam">__file__</span><span class="op">)</span><span class="op">.</span><span class="nam">parent</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t14" href="#t14">14</a></span><span class="t"><span class="nam">CONFIG_DIR</span> <span class="op">=</span> <span class="nam">SCRIPT_DIR</span><span class="op">.</span><span class="nam">parent</span> <span class="op">/</span> <span class="str">"config"</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t15" href="#t15">15</a></span><span class="t"><span class="nam">SOURCES_FILE</span> <span class="op">=</span> <span class="nam">CONFIG_DIR</span> <span class="op">/</span> <span class="str">"config.json"</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t16" href="#t16">16</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t17" href="#t17">17</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t18" href="#t18">18</a></span><span class="t"><span class="key">def</span> <span class="nam">load_sources</span><span class="op">(</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t19" href="#t19">19</a></span><span class="t"> <span class="str">"""Load current sources configuration."""</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t20" href="#t20">20</a></span><span class="t"> <span class="key">if</span> <span class="nam">SOURCES_FILE</span><span class="op">.</span><span class="nam">exists</span><span class="op">(</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t21" href="#t21">21</a></span><span class="t"> <span class="key">with</span> <span class="nam">open</span><span class="op">(</span><span class="nam">SOURCES_FILE</span><span class="op">,</span> <span class="str">'r'</span><span class="op">)</span> <span class="key">as</span> <span class="nam">f</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t22" href="#t22">22</a></span><span class="t"> <span class="key">return</span> <span class="nam">json</span><span class="op">.</span><span class="nam">load</span><span class="op">(</span><span class="nam">f</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t23" href="#t23">23</a></span><span class="t"> <span class="key">return</span> <span class="nam">get_default_sources</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t24" href="#t24">24</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t25" href="#t25">25</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t26" href="#t26">26</a></span><span class="t"><span class="key">def</span> <span class="nam">save_sources</span><span class="op">(</span><span class="nam">sources</span><span class="op">:</span> <span class="nam">dict</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t27" href="#t27">27</a></span><span class="t"> <span class="str">"""Save sources configuration."""</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t28" href="#t28">28</a></span><span class="t"> <span class="nam">CONFIG_DIR</span><span class="op">.</span><span class="nam">mkdir</span><span class="op">(</span><span class="nam">parents</span><span class="op">=</span><span class="key">True</span><span class="op">,</span> <span class="nam">exist_ok</span><span class="op">=</span><span class="key">True</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t29" href="#t29">29</a></span><span class="t"> <span class="key">with</span> <span class="nam">open</span><span class="op">(</span><span class="nam">SOURCES_FILE</span><span class="op">,</span> <span class="str">'w'</span><span class="op">)</span> <span class="key">as</span> <span class="nam">f</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t30" href="#t30">30</a></span><span class="t"> <span class="nam">json</span><span class="op">.</span><span class="nam">dump</span><span class="op">(</span><span class="nam">sources</span><span class="op">,</span> <span class="nam">f</span><span class="op">,</span> <span class="nam">indent</span><span class="op">=</span><span class="num">2</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t31" href="#t31">31</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"✅ Configuration saved to {SOURCES_FILE}"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t32" href="#t32">32</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t33" href="#t33">33</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t34" href="#t34">34</a></span><span class="t"><span class="key">def</span> <span class="nam">get_default_sources</span><span class="op">(</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t35" href="#t35">35</a></span><span class="t"> <span class="str">"""Return default source configuration."""</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t36" href="#t36">36</a></span><span class="t"> <span class="nam">config_path</span> <span class="op">=</span> <span class="nam">CONFIG_DIR</span> <span class="op">/</span> <span class="str">"config.json"</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t37" href="#t37">37</a></span><span class="t"> <span class="key">if</span> <span class="nam">config_path</span><span class="op">.</span><span class="nam">exists</span><span class="op">(</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t38" href="#t38">38</a></span><span class="t"> <span class="key">with</span> <span class="nam">open</span><span class="op">(</span><span class="nam">config_path</span><span class="op">,</span> <span class="str">'r'</span><span class="op">)</span> <span class="key">as</span> <span class="nam">f</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t39" href="#t39">39</a></span><span class="t"> <span class="key">return</span> <span class="nam">json</span><span class="op">.</span><span class="nam">load</span><span class="op">(</span><span class="nam">f</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t40" href="#t40">40</a></span><span class="t"> <span class="key">return</span> <span class="op">{</span><span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t41" href="#t41">41</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t42" href="#t42">42</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t43" href="#t43">43</a></span><span class="t"><span class="key">def</span> <span class="nam">prompt</span><span class="op">(</span><span class="nam">message</span><span class="op">:</span> <span class="nam">str</span><span class="op">,</span> <span class="nam">default</span><span class="op">:</span> <span class="nam">str</span> <span class="op">=</span> <span class="str">""</span><span class="op">)</span> <span class="op">-></span> <span class="nam">str</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t44" href="#t44">44</a></span><span class="t"> <span class="str">"""Prompt for input with optional default."""</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t45" href="#t45">45</a></span><span class="t"> <span class="key">if</span> <span class="nam">default</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t46" href="#t46">46</a></span><span class="t"> <span class="nam">result</span> <span class="op">=</span> <span class="nam">input</span><span class="op">(</span><span class="str">f"{message} [{default}]: "</span><span class="op">)</span><span class="op">.</span><span class="nam">strip</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t47" href="#t47">47</a></span><span class="t"> <span class="key">return</span> <span class="nam">result</span> <span class="key">if</span> <span class="nam">result</span> <span class="key">else</span> <span class="nam">default</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t48" href="#t48">48</a></span><span class="t"> <span class="key">return</span> <span class="nam">input</span><span class="op">(</span><span class="str">f"{message}: "</span><span class="op">)</span><span class="op">.</span><span class="nam">strip</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t49" href="#t49">49</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t50" href="#t50">50</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t51" href="#t51">51</a></span><span class="t"><span class="key">def</span> <span class="nam">prompt_bool</span><span class="op">(</span><span class="nam">message</span><span class="op">:</span> <span class="nam">str</span><span class="op">,</span> <span class="nam">default</span><span class="op">:</span> <span class="nam">bool</span> <span class="op">=</span> <span class="key">True</span><span class="op">)</span> <span class="op">-></span> <span class="nam">bool</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t52" href="#t52">52</a></span><span class="t"> <span class="str">"""Prompt for yes/no input."""</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t53" href="#t53">53</a></span><span class="t"> <span class="nam">default_str</span> <span class="op">=</span> <span class="str">"Y/n"</span> <span class="key">if</span> <span class="nam">default</span> <span class="key">else</span> <span class="str">"y/N"</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t54" href="#t54">54</a></span><span class="t"> <span class="nam">result</span> <span class="op">=</span> <span class="nam">input</span><span class="op">(</span><span class="str">f"{message} [{default_str}]: "</span><span class="op">)</span><span class="op">.</span><span class="nam">strip</span><span class="op">(</span><span class="op">)</span><span class="op">.</span><span class="nam">lower</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t55" href="#t55">55</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">result</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t56" href="#t56">56</a></span><span class="t"> <span class="key">return</span> <span class="nam">default</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t57" href="#t57">57</a></span><span class="t"> <span class="key">return</span> <span class="nam">result</span> <span class="key">in</span> <span class="op">(</span><span class="str">'y'</span><span class="op">,</span> <span class="str">'yes'</span><span class="op">,</span> <span class="str">'1'</span><span class="op">,</span> <span class="str">'true'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t58" href="#t58">58</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t59" href="#t59">59</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t60" href="#t60">60</a></span><span class="t"><span class="key">def</span> <span class="nam">setup_rss_feeds</span><span class="op">(</span><span class="nam">sources</span><span class="op">:</span> <span class="nam">dict</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t61" href="#t61">61</a></span><span class="t"> <span class="str">"""Interactive RSS feed configuration."""</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t62" href="#t62">62</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">"\n📰 RSS Feed Configuration\n"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t63" href="#t63">63</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">"Enable/disable news sources:\n"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t64" href="#t64">64</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t65" href="#t65">65</a></span><span class="t"> <span class="key">for</span> <span class="nam">feed_id</span><span class="op">,</span> <span class="nam">feed_config</span> <span class="key">in</span> <span class="nam">sources</span><span class="op">[</span><span class="str">'rss_feeds'</span><span class="op">]</span><span class="op">.</span><span class="nam">items</span><span class="op">(</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t66" href="#t66">66</a></span><span class="t"> <span class="nam">name</span> <span class="op">=</span> <span class="nam">feed_config</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'name'</span><span class="op">,</span> <span class="nam">feed_id</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t67" href="#t67">67</a></span><span class="t"> <span class="nam">current</span> <span class="op">=</span> <span class="nam">feed_config</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'enabled'</span><span class="op">,</span> <span class="key">True</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t68" href="#t68">68</a></span><span class="t"> <span class="nam">enabled</span> <span class="op">=</span> <span class="nam">prompt_bool</span><span class="op">(</span><span class="str">f" {name}"</span><span class="op">,</span> <span class="nam">current</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t69" href="#t69">69</a></span><span class="t"> <span class="nam">sources</span><span class="op">[</span><span class="str">'rss_feeds'</span><span class="op">]</span><span class="op">[</span><span class="nam">feed_id</span><span class="op">]</span><span class="op">[</span><span class="str">'enabled'</span><span class="op">]</span> <span class="op">=</span> <span class="nam">enabled</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t70" href="#t70">70</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t71" href="#t71">71</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">"\n Add custom RSS feed? (leave blank to skip)"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t72" href="#t72">72</a></span><span class="t"> <span class="nam">custom_name</span> <span class="op">=</span> <span class="nam">prompt</span><span class="op">(</span><span class="str">" Feed name"</span><span class="op">,</span> <span class="str">""</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t73" href="#t73">73</a></span><span class="t"> <span class="key">if</span> <span class="nam">custom_name</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t74" href="#t74">74</a></span><span class="t"> <span class="nam">custom_url</span> <span class="op">=</span> <span class="nam">prompt</span><span class="op">(</span><span class="str">" Feed URL"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t75" href="#t75">75</a></span><span class="t"> <span class="nam">sources</span><span class="op">[</span><span class="str">'rss_feeds'</span><span class="op">]</span><span class="op">[</span><span class="nam">custom_name</span><span class="op">.</span><span class="nam">lower</span><span class="op">(</span><span class="op">)</span><span class="op">.</span><span class="nam">replace</span><span class="op">(</span><span class="str">' '</span><span class="op">,</span> <span class="str">'_'</span><span class="op">)</span><span class="op">]</span> <span class="op">=</span> <span class="op">{</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t76" href="#t76">76</a></span><span class="t"> <span class="str">"name"</span><span class="op">:</span> <span class="nam">custom_name</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t77" href="#t77">77</a></span><span class="t"> <span class="str">"enabled"</span><span class="op">:</span> <span class="key">True</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t78" href="#t78">78</a></span><span class="t"> <span class="str">"main"</span><span class="op">:</span> <span class="nam">custom_url</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t79" href="#t79">79</a></span><span class="t"> <span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t80" href="#t80">80</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f" ✅ Added {custom_name}"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t81" href="#t81">81</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t82" href="#t82">82</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t83" href="#t83">83</a></span><span class="t"><span class="key">def</span> <span class="nam">setup_markets</span><span class="op">(</span><span class="nam">sources</span><span class="op">:</span> <span class="nam">dict</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t84" href="#t84">84</a></span><span class="t"> <span class="str">"""Interactive market configuration."""</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t85" href="#t85">85</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">"\n📊 Market Coverage\n"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t86" href="#t86">86</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">"Enable/disable market regions:\n"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t87" href="#t87">87</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t88" href="#t88">88</a></span><span class="t"> <span class="key">for</span> <span class="nam">market_id</span><span class="op">,</span> <span class="nam">market_config</span> <span class="key">in</span> <span class="nam">sources</span><span class="op">[</span><span class="str">'markets'</span><span class="op">]</span><span class="op">.</span><span class="nam">items</span><span class="op">(</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t89" href="#t89">89</a></span><span class="t"> <span class="nam">name</span> <span class="op">=</span> <span class="nam">market_config</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'name'</span><span class="op">,</span> <span class="nam">market_id</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t90" href="#t90">90</a></span><span class="t"> <span class="nam">current</span> <span class="op">=</span> <span class="nam">market_config</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'enabled'</span><span class="op">,</span> <span class="key">True</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t91" href="#t91">91</a></span><span class="t"> <span class="nam">enabled</span> <span class="op">=</span> <span class="nam">prompt_bool</span><span class="op">(</span><span class="str">f" {name}"</span><span class="op">,</span> <span class="nam">current</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t92" href="#t92">92</a></span><span class="t"> <span class="nam">sources</span><span class="op">[</span><span class="str">'markets'</span><span class="op">]</span><span class="op">[</span><span class="nam">market_id</span><span class="op">]</span><span class="op">[</span><span class="str">'enabled'</span><span class="op">]</span> <span class="op">=</span> <span class="nam">enabled</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t93" href="#t93">93</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t94" href="#t94">94</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t95" href="#t95">95</a></span><span class="t"><span class="key">def</span> <span class="nam">setup_delivery</span><span class="op">(</span><span class="nam">sources</span><span class="op">:</span> <span class="nam">dict</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t96" href="#t96">96</a></span><span class="t"> <span class="str">"""Interactive delivery channel configuration."""</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t97" href="#t97">97</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">"\n📤 Delivery Channels\n"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t98" href="#t98">98</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t99" href="#t99">99</a></span><span class="t"> <span class="com"># Ensure delivery dict exists</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t100" href="#t100">100</a></span><span class="t"> <span class="key">if</span> <span class="str">'delivery'</span> <span class="key">not</span> <span class="key">in</span> <span class="nam">sources</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t101" href="#t101">101</a></span><span class="t"> <span class="nam">sources</span><span class="op">[</span><span class="str">'delivery'</span><span class="op">]</span> <span class="op">=</span> <span class="op">{</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t102" href="#t102">102</a></span><span class="t"> <span class="str">'whatsapp'</span><span class="op">:</span> <span class="op">{</span><span class="str">'enabled'</span><span class="op">:</span> <span class="key">True</span><span class="op">,</span> <span class="str">'group'</span><span class="op">:</span> <span class="str">''</span><span class="op">}</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t103" href="#t103">103</a></span><span class="t"> <span class="str">'telegram'</span><span class="op">:</span> <span class="op">{</span><span class="str">'enabled'</span><span class="op">:</span> <span class="key">False</span><span class="op">,</span> <span class="str">'group'</span><span class="op">:</span> <span class="str">''</span><span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t104" href="#t104">104</a></span><span class="t"> <span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t105" href="#t105">105</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t106" href="#t106">106</a></span><span class="t"> <span class="com"># WhatsApp</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t107" href="#t107">107</a></span><span class="t"> <span class="nam">wa_enabled</span> <span class="op">=</span> <span class="nam">prompt_bool</span><span class="op">(</span><span class="str">"Enable WhatsApp delivery"</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t108" href="#t108">108</a></span><span class="t"> <span class="nam">sources</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'delivery'</span><span class="op">,</span> <span class="op">{</span><span class="op">}</span><span class="op">)</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'whatsapp'</span><span class="op">,</span> <span class="op">{</span><span class="op">}</span><span class="op">)</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'enabled'</span><span class="op">,</span> <span class="key">True</span><span class="op">)</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t109" href="#t109">109</a></span><span class="t"> <span class="nam">sources</span><span class="op">[</span><span class="str">'delivery'</span><span class="op">]</span><span class="op">[</span><span class="str">'whatsapp'</span><span class="op">]</span><span class="op">[</span><span class="str">'enabled'</span><span class="op">]</span> <span class="op">=</span> <span class="nam">wa_enabled</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t110" href="#t110">110</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t111" href="#t111">111</a></span><span class="t"> <span class="key">if</span> <span class="nam">wa_enabled</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t112" href="#t112">112</a></span><span class="t"> <span class="nam">wa_group</span> <span class="op">=</span> <span class="nam">prompt</span><span class="op">(</span><span class="str">" WhatsApp group name or JID"</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t113" href="#t113">113</a></span><span class="t"> <span class="nam">sources</span><span class="op">[</span><span class="str">'delivery'</span><span class="op">]</span><span class="op">[</span><span class="str">'whatsapp'</span><span class="op">]</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'group'</span><span class="op">,</span> <span class="str">''</span><span class="op">)</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t114" href="#t114">114</a></span><span class="t"> <span class="nam">sources</span><span class="op">[</span><span class="str">'delivery'</span><span class="op">]</span><span class="op">[</span><span class="str">'whatsapp'</span><span class="op">]</span><span class="op">[</span><span class="str">'group'</span><span class="op">]</span> <span class="op">=</span> <span class="nam">wa_group</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t115" href="#t115">115</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t116" href="#t116">116</a></span><span class="t"> <span class="com"># Telegram</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t117" href="#t117">117</a></span><span class="t"> <span class="nam">tg_enabled</span> <span class="op">=</span> <span class="nam">prompt_bool</span><span class="op">(</span><span class="str">"Enable Telegram delivery"</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t118" href="#t118">118</a></span><span class="t"> <span class="nam">sources</span><span class="op">[</span><span class="str">'delivery'</span><span class="op">]</span><span class="op">[</span><span class="str">'telegram'</span><span class="op">]</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'enabled'</span><span class="op">,</span> <span class="key">False</span><span class="op">)</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t119" href="#t119">119</a></span><span class="t"> <span class="nam">sources</span><span class="op">[</span><span class="str">'delivery'</span><span class="op">]</span><span class="op">[</span><span class="str">'telegram'</span><span class="op">]</span><span class="op">[</span><span class="str">'enabled'</span><span class="op">]</span> <span class="op">=</span> <span class="nam">tg_enabled</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t120" href="#t120">120</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t121" href="#t121">121</a></span><span class="t"> <span class="key">if</span> <span class="nam">tg_enabled</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t122" href="#t122">122</a></span><span class="t"> <span class="nam">tg_group</span> <span class="op">=</span> <span class="nam">prompt</span><span class="op">(</span><span class="str">" Telegram group name or ID"</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t123" href="#t123">123</a></span><span class="t"> <span class="nam">sources</span><span class="op">[</span><span class="str">'delivery'</span><span class="op">]</span><span class="op">[</span><span class="str">'telegram'</span><span class="op">]</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'group'</span><span class="op">,</span> <span class="str">''</span><span class="op">)</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t124" href="#t124">124</a></span><span class="t"> <span class="nam">sources</span><span class="op">[</span><span class="str">'delivery'</span><span class="op">]</span><span class="op">[</span><span class="str">'telegram'</span><span class="op">]</span><span class="op">[</span><span class="str">'group'</span><span class="op">]</span> <span class="op">=</span> <span class="nam">tg_group</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t125" href="#t125">125</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t126" href="#t126">126</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t127" href="#t127">127</a></span><span class="t"><span class="key">def</span> <span class="nam">setup_language</span><span class="op">(</span><span class="nam">sources</span><span class="op">:</span> <span class="nam">dict</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t128" href="#t128">128</a></span><span class="t"> <span class="str">"""Interactive language configuration."""</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t129" href="#t129">129</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">"\n🌐 Language Settings\n"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t130" href="#t130">130</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t131" href="#t131">131</a></span><span class="t"> <span class="nam">current_lang</span> <span class="op">=</span> <span class="nam">sources</span><span class="op">[</span><span class="str">'language'</span><span class="op">]</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'default'</span><span class="op">,</span> <span class="str">'de'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t132" href="#t132">132</a></span><span class="t"> <span class="nam">lang</span> <span class="op">=</span> <span class="nam">prompt</span><span class="op">(</span><span class="str">"Default language (de/en)"</span><span class="op">,</span> <span class="nam">current_lang</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t133" href="#t133">133</a></span><span class="t"> <span class="key">if</span> <span class="nam">lang</span> <span class="key">in</span> <span class="nam">sources</span><span class="op">[</span><span class="str">'language'</span><span class="op">]</span><span class="op">[</span><span class="str">'supported'</span><span class="op">]</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t134" href="#t134">134</a></span><span class="t"> <span class="nam">sources</span><span class="op">[</span><span class="str">'language'</span><span class="op">]</span><span class="op">[</span><span class="str">'default'</span><span class="op">]</span> <span class="op">=</span> <span class="nam">lang</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t135" href="#t135">135</a></span><span class="t"> <span class="key">else</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t136" href="#t136">136</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f" ⚠️ Unsupported language '{lang}', keeping '{current_lang}'"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t137" href="#t137">137</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t138" href="#t138">138</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t139" href="#t139">139</a></span><span class="t"><span class="key">def</span> <span class="nam">setup_schedule</span><span class="op">(</span><span class="nam">sources</span><span class="op">:</span> <span class="nam">dict</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t140" href="#t140">140</a></span><span class="t"> <span class="str">"""Interactive schedule configuration."""</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t141" href="#t141">141</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">"\n⏰ Briefing Schedule\n"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t142" href="#t142">142</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t143" href="#t143">143</a></span><span class="t"> <span class="com"># Morning</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t144" href="#t144">144</a></span><span class="t"> <span class="nam">morning</span> <span class="op">=</span> <span class="nam">sources</span><span class="op">[</span><span class="str">'schedule'</span><span class="op">]</span><span class="op">[</span><span class="str">'morning'</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t145" href="#t145">145</a></span><span class="t"> <span class="nam">morning_enabled</span> <span class="op">=</span> <span class="nam">prompt_bool</span><span class="op">(</span><span class="str">f"Enable morning briefing ({morning['description']})"</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t146" href="#t146">146</a></span><span class="t"> <span class="nam">morning</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'enabled'</span><span class="op">,</span> <span class="key">True</span><span class="op">)</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t147" href="#t147">147</a></span><span class="t"> <span class="nam">sources</span><span class="op">[</span><span class="str">'schedule'</span><span class="op">]</span><span class="op">[</span><span class="str">'morning'</span><span class="op">]</span><span class="op">[</span><span class="str">'enabled'</span><span class="op">]</span> <span class="op">=</span> <span class="nam">morning_enabled</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t148" href="#t148">148</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t149" href="#t149">149</a></span><span class="t"> <span class="key">if</span> <span class="nam">morning_enabled</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t150" href="#t150">150</a></span><span class="t"> <span class="nam">morning_cron</span> <span class="op">=</span> <span class="nam">prompt</span><span class="op">(</span><span class="str">" Morning cron expression"</span><span class="op">,</span> <span class="nam">morning</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'cron'</span><span class="op">,</span> <span class="str">'30 6 * * 1-5'</span><span class="op">)</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t151" href="#t151">151</a></span><span class="t"> <span class="nam">sources</span><span class="op">[</span><span class="str">'schedule'</span><span class="op">]</span><span class="op">[</span><span class="str">'morning'</span><span class="op">]</span><span class="op">[</span><span class="str">'cron'</span><span class="op">]</span> <span class="op">=</span> <span class="nam">morning_cron</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t152" href="#t152">152</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t153" href="#t153">153</a></span><span class="t"> <span class="com"># Evening</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t154" href="#t154">154</a></span><span class="t"> <span class="nam">evening</span> <span class="op">=</span> <span class="nam">sources</span><span class="op">[</span><span class="str">'schedule'</span><span class="op">]</span><span class="op">[</span><span class="str">'evening'</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t155" href="#t155">155</a></span><span class="t"> <span class="nam">evening_enabled</span> <span class="op">=</span> <span class="nam">prompt_bool</span><span class="op">(</span><span class="str">f"Enable evening briefing ({evening['description']})"</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t156" href="#t156">156</a></span><span class="t"> <span class="nam">evening</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'enabled'</span><span class="op">,</span> <span class="key">True</span><span class="op">)</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t157" href="#t157">157</a></span><span class="t"> <span class="nam">sources</span><span class="op">[</span><span class="str">'schedule'</span><span class="op">]</span><span class="op">[</span><span class="str">'evening'</span><span class="op">]</span><span class="op">[</span><span class="str">'enabled'</span><span class="op">]</span> <span class="op">=</span> <span class="nam">evening_enabled</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t158" href="#t158">158</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t159" href="#t159">159</a></span><span class="t"> <span class="key">if</span> <span class="nam">evening_enabled</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t160" href="#t160">160</a></span><span class="t"> <span class="nam">evening_cron</span> <span class="op">=</span> <span class="nam">prompt</span><span class="op">(</span><span class="str">" Evening cron expression"</span><span class="op">,</span> <span class="nam">evening</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'cron'</span><span class="op">,</span> <span class="str">'0 13 * * 1-5'</span><span class="op">)</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t161" href="#t161">161</a></span><span class="t"> <span class="nam">sources</span><span class="op">[</span><span class="str">'schedule'</span><span class="op">]</span><span class="op">[</span><span class="str">'evening'</span><span class="op">]</span><span class="op">[</span><span class="str">'cron'</span><span class="op">]</span> <span class="op">=</span> <span class="nam">evening_cron</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t162" href="#t162">162</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t163" href="#t163">163</a></span><span class="t"> <span class="com"># Timezone</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t164" href="#t164">164</a></span><span class="t"> <span class="nam">tz</span> <span class="op">=</span> <span class="nam">prompt</span><span class="op">(</span><span class="str">"Timezone"</span><span class="op">,</span> <span class="nam">sources</span><span class="op">[</span><span class="str">'schedule'</span><span class="op">]</span><span class="op">[</span><span class="str">'morning'</span><span class="op">]</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'timezone'</span><span class="op">,</span> <span class="str">'America/Los_Angeles'</span><span class="op">)</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t165" href="#t165">165</a></span><span class="t"> <span class="nam">sources</span><span class="op">[</span><span class="str">'schedule'</span><span class="op">]</span><span class="op">[</span><span class="str">'morning'</span><span class="op">]</span><span class="op">[</span><span class="str">'timezone'</span><span class="op">]</span> <span class="op">=</span> <span class="nam">tz</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t166" href="#t166">166</a></span><span class="t"> <span class="nam">sources</span><span class="op">[</span><span class="str">'schedule'</span><span class="op">]</span><span class="op">[</span><span class="str">'evening'</span><span class="op">]</span><span class="op">[</span><span class="str">'timezone'</span><span class="op">]</span> <span class="op">=</span> <span class="nam">tz</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t167" href="#t167">167</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t168" href="#t168">168</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t169" href="#t169">169</a></span><span class="t"><span class="key">def</span> <span class="nam">setup_cron_jobs</span><span class="op">(</span><span class="nam">sources</span><span class="op">:</span> <span class="nam">dict</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t170" href="#t170">170</a></span><span class="t"> <span class="str">"""Set up OpenClaw cron jobs based on configuration."""</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t171" href="#t171">171</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">"\n📅 Setting up cron jobs...\n"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t172" href="#t172">172</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t173" href="#t173">173</a></span><span class="t"> <span class="nam">schedule</span> <span class="op">=</span> <span class="nam">sources</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'schedule'</span><span class="op">,</span> <span class="op">{</span><span class="op">}</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t174" href="#t174">174</a></span><span class="t"> <span class="nam">delivery</span> <span class="op">=</span> <span class="nam">sources</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'delivery'</span><span class="op">,</span> <span class="op">{</span><span class="op">}</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t175" href="#t175">175</a></span><span class="t"> <span class="nam">language</span> <span class="op">=</span> <span class="nam">sources</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'language'</span><span class="op">,</span> <span class="op">{</span><span class="op">}</span><span class="op">)</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'default'</span><span class="op">,</span> <span class="str">'de'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t176" href="#t176">176</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t177" href="#t177">177</a></span><span class="t"> <span class="com"># Determine delivery target</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t178" href="#t178">178</a></span><span class="t"> <span class="key">if</span> <span class="nam">delivery</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'whatsapp'</span><span class="op">,</span> <span class="op">{</span><span class="op">}</span><span class="op">)</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'enabled'</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t179" href="#t179">179</a></span><span class="t"> <span class="nam">group</span> <span class="op">=</span> <span class="nam">delivery</span><span class="op">[</span><span class="str">'whatsapp'</span><span class="op">]</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'group'</span><span class="op">,</span> <span class="str">''</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t180" href="#t180">180</a></span><span class="t"> <span class="nam">send_cmd</span> <span class="op">=</span> <span class="str">f"--send --group '{group}'"</span> <span class="key">if</span> <span class="nam">group</span> <span class="key">else</span> <span class="str">""</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t181" href="#t181">181</a></span><span class="t"> <span class="key">elif</span> <span class="nam">delivery</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'telegram'</span><span class="op">,</span> <span class="op">{</span><span class="op">}</span><span class="op">)</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'enabled'</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t182" href="#t182">182</a></span><span class="t"> <span class="nam">group</span> <span class="op">=</span> <span class="nam">delivery</span><span class="op">[</span><span class="str">'telegram'</span><span class="op">]</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'group'</span><span class="op">,</span> <span class="str">''</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t183" href="#t183">183</a></span><span class="t"> <span class="nam">send_cmd</span> <span class="op">=</span> <span class="str">f"--send --group '{group}'"</span> <span class="com"># Would need telegram support</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t184" href="#t184">184</a></span><span class="t"> <span class="key">else</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t185" href="#t185">185</a></span><span class="t"> <span class="nam">send_cmd</span> <span class="op">=</span> <span class="str">""</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t186" href="#t186">186</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t187" href="#t187">187</a></span><span class="t"> <span class="com"># Morning job</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t188" href="#t188">188</a></span><span class="t"> <span class="key">if</span> <span class="nam">schedule</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'morning'</span><span class="op">,</span> <span class="op">{</span><span class="op">}</span><span class="op">)</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'enabled'</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t189" href="#t189">189</a></span><span class="t"> <span class="nam">morning_cron</span> <span class="op">=</span> <span class="nam">schedule</span><span class="op">[</span><span class="str">'morning'</span><span class="op">]</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'cron'</span><span class="op">,</span> <span class="str">'30 6 * * 1-5'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t190" href="#t190">190</a></span><span class="t"> <span class="nam">tz</span> <span class="op">=</span> <span class="nam">schedule</span><span class="op">[</span><span class="str">'morning'</span><span class="op">]</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'timezone'</span><span class="op">,</span> <span class="str">'America/Los_Angeles'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t191" href="#t191">191</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t192" href="#t192">192</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f" Creating morning briefing job: {morning_cron} ({tz})"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t193" href="#t193">193</a></span><span class="t"> <span class="com"># Note: Actual cron creation would happen via openclaw cron add</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t194" href="#t194">194</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f" ✅ Morning briefing configured"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t195" href="#t195">195</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t196" href="#t196">196</a></span><span class="t"> <span class="com"># Evening job</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t197" href="#t197">197</a></span><span class="t"> <span class="key">if</span> <span class="nam">schedule</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'evening'</span><span class="op">,</span> <span class="op">{</span><span class="op">}</span><span class="op">)</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'enabled'</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t198" href="#t198">198</a></span><span class="t"> <span class="nam">evening_cron</span> <span class="op">=</span> <span class="nam">schedule</span><span class="op">[</span><span class="str">'evening'</span><span class="op">]</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'cron'</span><span class="op">,</span> <span class="str">'0 13 * * 1-5'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t199" href="#t199">199</a></span><span class="t"> <span class="nam">tz</span> <span class="op">=</span> <span class="nam">schedule</span><span class="op">[</span><span class="str">'evening'</span><span class="op">]</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'timezone'</span><span class="op">,</span> <span class="str">'America/Los_Angeles'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t200" href="#t200">200</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t201" href="#t201">201</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f" Creating evening briefing job: {evening_cron} ({tz})"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t202" href="#t202">202</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f" ✅ Evening briefing configured"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t203" href="#t203">203</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t204" href="#t204">204</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t205" href="#t205">205</a></span><span class="t"><span class="key">def</span> <span class="nam">run_setup</span><span class="op">(</span><span class="nam">args</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t206" href="#t206">206</a></span><span class="t"> <span class="str">"""Run interactive setup wizard."""</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t207" href="#t207">207</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">"\n"</span> <span class="op">+</span> <span class="str">"="</span><span class="op">*</span><span class="num">60</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t208" href="#t208">208</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">"📈 Finance News Skill - Setup Wizard"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t209" href="#t209">209</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">"="</span><span class="op">*</span><span class="num">60</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t210" href="#t210">210</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t211" href="#t211">211</a></span><span class="t"> <span class="com"># Load existing or default config</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t212" href="#t212">212</a></span><span class="t"> <span class="key">if</span> <span class="nam">args</span><span class="op">.</span><span class="nam">reset</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t213" href="#t213">213</a></span><span class="t"> <span class="nam">sources</span> <span class="op">=</span> <span class="nam">get_default_sources</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t214" href="#t214">214</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">"\n⚠️ Starting with fresh configuration"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t215" href="#t215">215</a></span><span class="t"> <span class="key">else</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t216" href="#t216">216</a></span><span class="t"> <span class="nam">sources</span> <span class="op">=</span> <span class="nam">load_sources</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t217" href="#t217">217</a></span><span class="t"> <span class="key">if</span> <span class="nam">SOURCES_FILE</span><span class="op">.</span><span class="nam">exists</span><span class="op">(</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t218" href="#t218">218</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"\n📂 Loaded existing configuration from {SOURCES_FILE}"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t219" href="#t219">219</a></span><span class="t"> <span class="key">else</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t220" href="#t220">220</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">"\n📂 No existing configuration found, using defaults"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t221" href="#t221">221</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t222" href="#t222">222</a></span><span class="t"> <span class="com"># Run through each section</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t223" href="#t223">223</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">args</span><span class="op">.</span><span class="nam">section</span> <span class="key">or</span> <span class="nam">args</span><span class="op">.</span><span class="nam">section</span> <span class="op">==</span> <span class="str">'feeds'</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t224" href="#t224">224</a></span><span class="t"> <span class="nam">setup_rss_feeds</span><span class="op">(</span><span class="nam">sources</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t225" href="#t225">225</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t226" href="#t226">226</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">args</span><span class="op">.</span><span class="nam">section</span> <span class="key">or</span> <span class="nam">args</span><span class="op">.</span><span class="nam">section</span> <span class="op">==</span> <span class="str">'markets'</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t227" href="#t227">227</a></span><span class="t"> <span class="nam">setup_markets</span><span class="op">(</span><span class="nam">sources</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t228" href="#t228">228</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t229" href="#t229">229</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">args</span><span class="op">.</span><span class="nam">section</span> <span class="key">or</span> <span class="nam">args</span><span class="op">.</span><span class="nam">section</span> <span class="op">==</span> <span class="str">'delivery'</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t230" href="#t230">230</a></span><span class="t"> <span class="nam">setup_delivery</span><span class="op">(</span><span class="nam">sources</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t231" href="#t231">231</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t232" href="#t232">232</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">args</span><span class="op">.</span><span class="nam">section</span> <span class="key">or</span> <span class="nam">args</span><span class="op">.</span><span class="nam">section</span> <span class="op">==</span> <span class="str">'language'</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t233" href="#t233">233</a></span><span class="t"> <span class="nam">setup_language</span><span class="op">(</span><span class="nam">sources</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t234" href="#t234">234</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t235" href="#t235">235</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">args</span><span class="op">.</span><span class="nam">section</span> <span class="key">or</span> <span class="nam">args</span><span class="op">.</span><span class="nam">section</span> <span class="op">==</span> <span class="str">'schedule'</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t236" href="#t236">236</a></span><span class="t"> <span class="nam">setup_schedule</span><span class="op">(</span><span class="nam">sources</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t237" href="#t237">237</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t238" href="#t238">238</a></span><span class="t"> <span class="com"># Save configuration</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t239" href="#t239">239</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">"\n"</span> <span class="op">+</span> <span class="str">"-"</span><span class="op">*</span><span class="num">60</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t240" href="#t240">240</a></span><span class="t"> <span class="key">if</span> <span class="nam">prompt_bool</span><span class="op">(</span><span class="str">"Save configuration?"</span><span class="op">,</span> <span class="key">True</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t241" href="#t241">241</a></span><span class="t"> <span class="nam">save_sources</span><span class="op">(</span><span class="nam">sources</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t242" href="#t242">242</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t243" href="#t243">243</a></span><span class="t"> <span class="com"># Set up cron jobs</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t244" href="#t244">244</a></span><span class="t"> <span class="key">if</span> <span class="nam">prompt_bool</span><span class="op">(</span><span class="str">"Set up cron jobs now?"</span><span class="op">,</span> <span class="key">True</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t245" href="#t245">245</a></span><span class="t"> <span class="nam">setup_cron_jobs</span><span class="op">(</span><span class="nam">sources</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t246" href="#t246">246</a></span><span class="t"> <span class="key">else</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t247" href="#t247">247</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">"❌ Configuration not saved"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t248" href="#t248">248</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t249" href="#t249">249</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">"\n✅ Setup complete!"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t250" href="#t250">250</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">"\nNext steps:"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t251" href="#t251">251</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">" • Run 'finance-news portfolio-list' to check your watchlist"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t252" href="#t252">252</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">" • Run 'finance-news briefing --morning' to test a briefing"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t253" href="#t253">253</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">" • Run 'finance-news market' to see market overview"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t254" href="#t254">254</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t255" href="#t255">255</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t256" href="#t256">256</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t257" href="#t257">257</a></span><span class="t"><span class="key">def</span> <span class="nam">show_config</span><span class="op">(</span><span class="nam">args</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t258" href="#t258">258</a></span><span class="t"> <span class="str">"""Show current configuration."""</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t259" href="#t259">259</a></span><span class="t"> <span class="nam">sources</span> <span class="op">=</span> <span class="nam">load_sources</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t260" href="#t260">260</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="nam">json</span><span class="op">.</span><span class="nam">dumps</span><span class="op">(</span><span class="nam">sources</span><span class="op">,</span> <span class="nam">indent</span><span class="op">=</span><span class="num">2</span><span class="op">)</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t261" href="#t261">261</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t262" href="#t262">262</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t263" href="#t263">263</a></span><span class="t"><span class="key">def</span> <span class="nam">main</span><span class="op">(</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t264" href="#t264">264</a></span><span class="t"> <span class="nam">parser</span> <span class="op">=</span> <span class="nam">argparse</span><span class="op">.</span><span class="nam">ArgumentParser</span><span class="op">(</span><span class="nam">description</span><span class="op">=</span><span class="str">'Finance News Setup'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t265" href="#t265">265</a></span><span class="t"> <span class="nam">subparsers</span> <span class="op">=</span> <span class="nam">parser</span><span class="op">.</span><span class="nam">add_subparsers</span><span class="op">(</span><span class="nam">dest</span><span class="op">=</span><span class="str">'command'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t266" href="#t266">266</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t267" href="#t267">267</a></span><span class="t"> <span class="com"># Setup command (default)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t268" href="#t268">268</a></span><span class="t"> <span class="nam">setup_parser</span> <span class="op">=</span> <span class="nam">subparsers</span><span class="op">.</span><span class="nam">add_parser</span><span class="op">(</span><span class="str">'wizard'</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">'Run setup wizard'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t269" href="#t269">269</a></span><span class="t"> <span class="nam">setup_parser</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">'--reset'</span><span class="op">,</span> <span class="nam">action</span><span class="op">=</span><span class="str">'store_true'</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">'Reset to defaults'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t270" href="#t270">270</a></span><span class="t"> <span class="nam">setup_parser</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">'--section'</span><span class="op">,</span> <span class="nam">choices</span><span class="op">=</span><span class="op">[</span><span class="str">'feeds'</span><span class="op">,</span> <span class="str">'markets'</span><span class="op">,</span> <span class="str">'delivery'</span><span class="op">,</span> <span class="str">'language'</span><span class="op">,</span> <span class="str">'schedule'</span><span class="op">]</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t271" href="#t271">271</a></span><span class="t"> <span class="nam">help</span><span class="op">=</span><span class="str">'Configure specific section only'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t272" href="#t272">272</a></span><span class="t"> <span class="nam">setup_parser</span><span class="op">.</span><span class="nam">set_defaults</span><span class="op">(</span><span class="nam">func</span><span class="op">=</span><span class="nam">run_setup</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t273" href="#t273">273</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t274" href="#t274">274</a></span><span class="t"> <span class="com"># Show config</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t275" href="#t275">275</a></span><span class="t"> <span class="nam">show_parser</span> <span class="op">=</span> <span class="nam">subparsers</span><span class="op">.</span><span class="nam">add_parser</span><span class="op">(</span><span class="str">'show'</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">'Show current configuration'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t276" href="#t276">276</a></span><span class="t"> <span class="nam">show_parser</span><span class="op">.</span><span class="nam">set_defaults</span><span class="op">(</span><span class="nam">func</span><span class="op">=</span><span class="nam">show_config</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t277" href="#t277">277</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t278" href="#t278">278</a></span><span class="t"> <span class="nam">args</span> <span class="op">=</span> <span class="nam">parser</span><span class="op">.</span><span class="nam">parse_args</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t279" href="#t279">279</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t280" href="#t280">280</a></span><span class="t"> <span class="key">if</span> <span class="nam">args</span><span class="op">.</span><span class="nam">command</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t281" href="#t281">281</a></span><span class="t"> <span class="nam">args</span><span class="op">.</span><span class="nam">func</span><span class="op">(</span><span class="nam">args</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t282" href="#t282">282</a></span><span class="t"> <span class="key">else</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t283" href="#t283">283</a></span><span class="t"> <span class="com"># Default to wizard</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t284" href="#t284">284</a></span><span class="t"> <span class="nam">args</span><span class="op">.</span><span class="nam">reset</span> <span class="op">=</span> <span class="key">False</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t285" href="#t285">285</a></span><span class="t"> <span class="nam">args</span><span class="op">.</span><span class="nam">section</span> <span class="op">=</span> <span class="key">None</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t286" href="#t286">286</a></span><span class="t"> <span class="nam">run_setup</span><span class="op">(</span><span class="nam">args</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t287" href="#t287">287</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t288" href="#t288">288</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="exc show_exc"><span class="n"><a id="t289" href="#t289">289</a></span><span class="t"><span class="key">if</span> <span class="nam">__name__</span> <span class="op">==</span> <span class="str">'__main__'</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="exc show_exc"><span class="n"><a id="t290" href="#t290">290</a></span><span class="t"> <span class="nam">main</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
</main>
|
||||
<footer>
|
||||
<div class="content">
|
||||
<p>
|
||||
<a class="nav" href="z_de1a740d5dc98ffd_research_py.html">« prev</a>
|
||||
<a class="nav" href="index.html">^ index</a>
|
||||
<a class="nav" href="z_de1a740d5dc98ffd_stocks_py.html">» next</a>
|
||||
|
||||
<a class="nav" href="https://coverage.readthedocs.io/en/7.13.2">coverage.py v7.13.2</a>,
|
||||
created at 2026-02-01 16:34 -0800
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
432
htmlcov/z_de1a740d5dc98ffd_stocks_py.html
generated
Normal file
432
htmlcov/z_de1a740d5dc98ffd_stocks_py.html
generated
Normal file
@@ -0,0 +1,432 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>Coverage for scripts/stocks.py: 53%</title>
|
||||
<link rel="icon" sizes="32x32" href="favicon_32_cb_c827f16f.png">
|
||||
<link rel="stylesheet" href="style_cb_9ff733b0.css" type="text/css">
|
||||
<script src="coverage_html_cb_dd2e7eb5.js" defer></script>
|
||||
</head>
|
||||
<body class="pyfile">
|
||||
<header>
|
||||
<div class="content">
|
||||
<h1>
|
||||
<span class="text">Coverage for </span><b>scripts / stocks.py</b>:
|
||||
<span class="pc_cov">53%</span>
|
||||
</h1>
|
||||
<aside id="help_panel_wrapper">
|
||||
<input id="help_panel_state" type="checkbox">
|
||||
<label for="help_panel_state">
|
||||
<img id="keyboard_icon" src="keybd_closed_cb_900cfef5.png" alt="Show/hide keyboard shortcuts">
|
||||
</label>
|
||||
<div id="help_panel">
|
||||
<p class="legend">Shortcuts on this page</p>
|
||||
<div class="keyhelp">
|
||||
<p>
|
||||
<kbd>r</kbd>
|
||||
<kbd>m</kbd>
|
||||
<kbd>x</kbd>
|
||||
toggle line displays
|
||||
</p>
|
||||
<p>
|
||||
<kbd>j</kbd>
|
||||
<kbd>k</kbd>
|
||||
next/prev highlighted chunk
|
||||
</p>
|
||||
<p>
|
||||
<kbd>0</kbd> (zero) top of page
|
||||
</p>
|
||||
<p>
|
||||
<kbd>1</kbd> (one) first highlighted chunk
|
||||
</p>
|
||||
<p>
|
||||
<kbd>[</kbd>
|
||||
<kbd>]</kbd>
|
||||
prev/next file
|
||||
</p>
|
||||
<p>
|
||||
<kbd>u</kbd> up to the index
|
||||
</p>
|
||||
<p>
|
||||
<kbd>?</kbd> show/hide this help
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
<h2>
|
||||
<span class="text">184 statements </span>
|
||||
<button type="button" class="run button_toggle_run" value="run" data-shortcut="r" title="Toggle lines run">97<span class="text"> run</span></button>
|
||||
<button type="button" class="mis show_mis button_toggle_mis" value="mis" data-shortcut="m" title="Toggle lines missing">87<span class="text"> missing</span></button>
|
||||
<button type="button" class="exc show_exc button_toggle_exc" value="exc" data-shortcut="x" title="Toggle lines excluded">2<span class="text"> excluded</span></button>
|
||||
</h2>
|
||||
<p class="text">
|
||||
<a id="prevFileLink" class="nav" href="z_de1a740d5dc98ffd_setup_py.html">« prev</a>
|
||||
<a id="indexLink" class="nav" href="index.html">^ index</a>
|
||||
<a id="nextFileLink" class="nav" href="z_de1a740d5dc98ffd_summarize_py.html">» next</a>
|
||||
|
||||
<a class="nav" href="https://coverage.readthedocs.io/en/7.13.2">coverage.py v7.13.2</a>,
|
||||
created at 2026-02-01 16:34 -0800
|
||||
</p>
|
||||
<aside class="hidden">
|
||||
<button type="button" class="button_next_chunk" data-shortcut="j"></button>
|
||||
<button type="button" class="button_prev_chunk" data-shortcut="k"></button>
|
||||
<button type="button" class="button_top_of_page" data-shortcut="0"></button>
|
||||
<button type="button" class="button_first_chunk" data-shortcut="1"></button>
|
||||
<button type="button" class="button_prev_file" data-shortcut="["></button>
|
||||
<button type="button" class="button_next_file" data-shortcut="]"></button>
|
||||
<button type="button" class="button_to_index" data-shortcut="u"></button>
|
||||
<button type="button" class="button_show_hide_help" data-shortcut="?"></button>
|
||||
</aside>
|
||||
</div>
|
||||
</header>
|
||||
<main id="source">
|
||||
<p class="pln"><span class="n"><a id="t1" href="#t1">1</a></span><span class="t"><span class="com">#!/usr/bin/env python3</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t2" href="#t2">2</a></span><span class="t"><span class="str">"""</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t3" href="#t3">3</a></span><span class="t"><span class="str">stocks.py - Unified stock management for holdings and watchlist.</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t4" href="#t4">4</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t5" href="#t5">5</a></span><span class="t"><span class="str">Single source of truth for:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t6" href="#t6">6</a></span><span class="t"><span class="str">- Holdings (stocks you own)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t7" href="#t7">7</a></span><span class="t"><span class="str">- Watchlist (stocks you're watching to buy)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t8" href="#t8">8</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t9" href="#t9">9</a></span><span class="t"><span class="str">Usage:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t10" href="#t10">10</a></span><span class="t"><span class="str"> from stocks import load_stocks, save_stocks, get_holdings, get_watchlist</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t11" href="#t11">11</a></span><span class="t"><span class="str"> from stocks import add_to_watchlist, add_to_holdings, move_to_holdings</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t12" href="#t12">12</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t13" href="#t13">13</a></span><span class="t"><span class="str">CLI:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t14" href="#t14">14</a></span><span class="t"><span class="str"> stocks.py list [--holdings|--watchlist]</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t15" href="#t15">15</a></span><span class="t"><span class="str"> stocks.py add-watchlist TICKER [--target 380] [--notes "Buy zone"]</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t16" href="#t16">16</a></span><span class="t"><span class="str"> stocks.py add-holding TICKER --name "Company" [--category "Tech"]</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t17" href="#t17">17</a></span><span class="t"><span class="str"> stocks.py move TICKER # watchlist → holdings (you bought it)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t18" href="#t18">18</a></span><span class="t"><span class="str"> stocks.py remove TICKER [--from holdings|watchlist]</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t19" href="#t19">19</a></span><span class="t"><span class="str">"""</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t20" href="#t20">20</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t21" href="#t21">21</a></span><span class="t"><span class="key">import</span> <span class="nam">argparse</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t22" href="#t22">22</a></span><span class="t"><span class="key">import</span> <span class="nam">json</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t23" href="#t23">23</a></span><span class="t"><span class="key">import</span> <span class="nam">sys</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t24" href="#t24">24</a></span><span class="t"><span class="key">from</span> <span class="nam">datetime</span> <span class="key">import</span> <span class="nam">datetime</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t25" href="#t25">25</a></span><span class="t"><span class="key">from</span> <span class="nam">pathlib</span> <span class="key">import</span> <span class="nam">Path</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t26" href="#t26">26</a></span><span class="t"><span class="key">from</span> <span class="nam">typing</span> <span class="key">import</span> <span class="nam">Optional</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t27" href="#t27">27</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t28" href="#t28">28</a></span><span class="t"><span class="com"># Default path - can be overridden</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t29" href="#t29">29</a></span><span class="t"><span class="nam">STOCKS_FILE</span> <span class="op">=</span> <span class="nam">Path</span><span class="op">(</span><span class="nam">__file__</span><span class="op">)</span><span class="op">.</span><span class="nam">parent</span><span class="op">.</span><span class="nam">parent</span> <span class="op">/</span> <span class="str">"config"</span> <span class="op">/</span> <span class="str">"stocks.json"</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t30" href="#t30">30</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t31" href="#t31">31</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t32" href="#t32">32</a></span><span class="t"><span class="key">def</span> <span class="nam">load_stocks</span><span class="op">(</span><span class="nam">path</span><span class="op">:</span> <span class="nam">Optional</span><span class="op">[</span><span class="nam">Path</span><span class="op">]</span> <span class="op">=</span> <span class="key">None</span><span class="op">)</span> <span class="op">-></span> <span class="nam">dict</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t33" href="#t33">33</a></span><span class="t"> <span class="str">"""Load the unified stocks file."""</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t34" href="#t34">34</a></span><span class="t"> <span class="nam">path</span> <span class="op">=</span> <span class="nam">path</span> <span class="key">or</span> <span class="nam">STOCKS_FILE</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t35" href="#t35">35</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">path</span><span class="op">.</span><span class="nam">exists</span><span class="op">(</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t36" href="#t36">36</a></span><span class="t"> <span class="key">return</span> <span class="op">{</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t37" href="#t37">37</a></span><span class="t"> <span class="str">"version"</span><span class="op">:</span> <span class="str">"1.0"</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t38" href="#t38">38</a></span><span class="t"> <span class="str">"updated"</span><span class="op">:</span> <span class="nam">datetime</span><span class="op">.</span><span class="nam">now</span><span class="op">(</span><span class="op">)</span><span class="op">.</span><span class="nam">strftime</span><span class="op">(</span><span class="str">"%Y-%m-%d"</span><span class="op">)</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t39" href="#t39">39</a></span><span class="t"> <span class="str">"holdings"</span><span class="op">:</span> <span class="op">[</span><span class="op">]</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t40" href="#t40">40</a></span><span class="t"> <span class="str">"watchlist"</span><span class="op">:</span> <span class="op">[</span><span class="op">]</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t41" href="#t41">41</a></span><span class="t"> <span class="str">"alert_definitions"</span><span class="op">:</span> <span class="op">{</span><span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t42" href="#t42">42</a></span><span class="t"> <span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t43" href="#t43">43</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t44" href="#t44">44</a></span><span class="t"> <span class="key">with</span> <span class="nam">open</span><span class="op">(</span><span class="nam">path</span><span class="op">,</span> <span class="str">'r'</span><span class="op">)</span> <span class="key">as</span> <span class="nam">f</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t45" href="#t45">45</a></span><span class="t"> <span class="key">return</span> <span class="nam">json</span><span class="op">.</span><span class="nam">load</span><span class="op">(</span><span class="nam">f</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t46" href="#t46">46</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t47" href="#t47">47</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t48" href="#t48">48</a></span><span class="t"><span class="key">def</span> <span class="nam">save_stocks</span><span class="op">(</span><span class="nam">data</span><span class="op">:</span> <span class="nam">dict</span><span class="op">,</span> <span class="nam">path</span><span class="op">:</span> <span class="nam">Optional</span><span class="op">[</span><span class="nam">Path</span><span class="op">]</span> <span class="op">=</span> <span class="key">None</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t49" href="#t49">49</a></span><span class="t"> <span class="str">"""Save the unified stocks file."""</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t50" href="#t50">50</a></span><span class="t"> <span class="nam">path</span> <span class="op">=</span> <span class="nam">path</span> <span class="key">or</span> <span class="nam">STOCKS_FILE</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t51" href="#t51">51</a></span><span class="t"> <span class="nam">data</span><span class="op">[</span><span class="str">"updated"</span><span class="op">]</span> <span class="op">=</span> <span class="nam">datetime</span><span class="op">.</span><span class="nam">now</span><span class="op">(</span><span class="op">)</span><span class="op">.</span><span class="nam">strftime</span><span class="op">(</span><span class="str">"%Y-%m-%d"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t52" href="#t52">52</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t53" href="#t53">53</a></span><span class="t"> <span class="key">with</span> <span class="nam">open</span><span class="op">(</span><span class="nam">path</span><span class="op">,</span> <span class="str">'w'</span><span class="op">)</span> <span class="key">as</span> <span class="nam">f</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t54" href="#t54">54</a></span><span class="t"> <span class="nam">json</span><span class="op">.</span><span class="nam">dump</span><span class="op">(</span><span class="nam">data</span><span class="op">,</span> <span class="nam">f</span><span class="op">,</span> <span class="nam">indent</span><span class="op">=</span><span class="num">2</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t55" href="#t55">55</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t56" href="#t56">56</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t57" href="#t57">57</a></span><span class="t"><span class="key">def</span> <span class="nam">get_holdings</span><span class="op">(</span><span class="nam">data</span><span class="op">:</span> <span class="nam">Optional</span><span class="op">[</span><span class="nam">dict</span><span class="op">]</span> <span class="op">=</span> <span class="key">None</span><span class="op">)</span> <span class="op">-></span> <span class="nam">list</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t58" href="#t58">58</a></span><span class="t"> <span class="str">"""Get list of holdings."""</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t59" href="#t59">59</a></span><span class="t"> <span class="key">if</span> <span class="nam">data</span> <span class="key">is</span> <span class="key">None</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t60" href="#t60">60</a></span><span class="t"> <span class="nam">data</span> <span class="op">=</span> <span class="nam">load_stocks</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t61" href="#t61">61</a></span><span class="t"> <span class="key">return</span> <span class="nam">data</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"holdings"</span><span class="op">,</span> <span class="op">[</span><span class="op">]</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t62" href="#t62">62</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t63" href="#t63">63</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t64" href="#t64">64</a></span><span class="t"><span class="key">def</span> <span class="nam">get_watchlist</span><span class="op">(</span><span class="nam">data</span><span class="op">:</span> <span class="nam">Optional</span><span class="op">[</span><span class="nam">dict</span><span class="op">]</span> <span class="op">=</span> <span class="key">None</span><span class="op">)</span> <span class="op">-></span> <span class="nam">list</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t65" href="#t65">65</a></span><span class="t"> <span class="str">"""Get list of watchlist items."""</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t66" href="#t66">66</a></span><span class="t"> <span class="key">if</span> <span class="nam">data</span> <span class="key">is</span> <span class="key">None</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t67" href="#t67">67</a></span><span class="t"> <span class="nam">data</span> <span class="op">=</span> <span class="nam">load_stocks</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t68" href="#t68">68</a></span><span class="t"> <span class="key">return</span> <span class="nam">data</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"watchlist"</span><span class="op">,</span> <span class="op">[</span><span class="op">]</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t69" href="#t69">69</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t70" href="#t70">70</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t71" href="#t71">71</a></span><span class="t"><span class="key">def</span> <span class="nam">get_holding_tickers</span><span class="op">(</span><span class="nam">data</span><span class="op">:</span> <span class="nam">Optional</span><span class="op">[</span><span class="nam">dict</span><span class="op">]</span> <span class="op">=</span> <span class="key">None</span><span class="op">)</span> <span class="op">-></span> <span class="nam">set</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t72" href="#t72">72</a></span><span class="t"> <span class="str">"""Get set of holding tickers for quick lookup."""</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t73" href="#t73">73</a></span><span class="t"> <span class="nam">holdings</span> <span class="op">=</span> <span class="nam">get_holdings</span><span class="op">(</span><span class="nam">data</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t74" href="#t74">74</a></span><span class="t"> <span class="key">return</span> <span class="op">{</span><span class="nam">h</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"ticker"</span><span class="op">)</span> <span class="key">for</span> <span class="nam">h</span> <span class="key">in</span> <span class="nam">holdings</span><span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t75" href="#t75">75</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t76" href="#t76">76</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t77" href="#t77">77</a></span><span class="t"><span class="key">def</span> <span class="nam">get_watchlist_tickers</span><span class="op">(</span><span class="nam">data</span><span class="op">:</span> <span class="nam">Optional</span><span class="op">[</span><span class="nam">dict</span><span class="op">]</span> <span class="op">=</span> <span class="key">None</span><span class="op">)</span> <span class="op">-></span> <span class="nam">set</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t78" href="#t78">78</a></span><span class="t"> <span class="str">"""Get set of watchlist tickers for quick lookup."""</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t79" href="#t79">79</a></span><span class="t"> <span class="nam">watchlist</span> <span class="op">=</span> <span class="nam">get_watchlist</span><span class="op">(</span><span class="nam">data</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t80" href="#t80">80</a></span><span class="t"> <span class="key">return</span> <span class="op">{</span><span class="nam">w</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"ticker"</span><span class="op">)</span> <span class="key">for</span> <span class="nam">w</span> <span class="key">in</span> <span class="nam">watchlist</span><span class="op">}</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t81" href="#t81">81</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t82" href="#t82">82</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t83" href="#t83">83</a></span><span class="t"><span class="key">def</span> <span class="nam">add_to_watchlist</span><span class="op">(</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t84" href="#t84">84</a></span><span class="t"> <span class="nam">ticker</span><span class="op">:</span> <span class="nam">str</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t85" href="#t85">85</a></span><span class="t"> <span class="nam">target</span><span class="op">:</span> <span class="nam">Optional</span><span class="op">[</span><span class="nam">float</span><span class="op">]</span> <span class="op">=</span> <span class="key">None</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t86" href="#t86">86</a></span><span class="t"> <span class="nam">stop</span><span class="op">:</span> <span class="nam">Optional</span><span class="op">[</span><span class="nam">float</span><span class="op">]</span> <span class="op">=</span> <span class="key">None</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t87" href="#t87">87</a></span><span class="t"> <span class="nam">notes</span><span class="op">:</span> <span class="nam">str</span> <span class="op">=</span> <span class="str">""</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t88" href="#t88">88</a></span><span class="t"> <span class="nam">alerts</span><span class="op">:</span> <span class="nam">Optional</span><span class="op">[</span><span class="nam">list</span><span class="op">]</span> <span class="op">=</span> <span class="key">None</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t89" href="#t89">89</a></span><span class="t"><span class="op">)</span> <span class="op">-></span> <span class="nam">bool</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t90" href="#t90">90</a></span><span class="t"> <span class="str">"""Add a stock to the watchlist."""</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t91" href="#t91">91</a></span><span class="t"> <span class="nam">data</span> <span class="op">=</span> <span class="nam">load_stocks</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t92" href="#t92">92</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t93" href="#t93">93</a></span><span class="t"> <span class="com"># Check if already in watchlist</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t94" href="#t94">94</a></span><span class="t"> <span class="key">for</span> <span class="nam">w</span> <span class="key">in</span> <span class="nam">data</span><span class="op">[</span><span class="str">"watchlist"</span><span class="op">]</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t95" href="#t95">95</a></span><span class="t"> <span class="key">if</span> <span class="nam">w</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"ticker"</span><span class="op">)</span> <span class="op">==</span> <span class="nam">ticker</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t96" href="#t96">96</a></span><span class="t"> <span class="com"># Update existing</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t97" href="#t97">97</a></span><span class="t"> <span class="key">if</span> <span class="nam">target</span> <span class="key">is</span> <span class="key">not</span> <span class="key">None</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t98" href="#t98">98</a></span><span class="t"> <span class="nam">w</span><span class="op">[</span><span class="str">"target"</span><span class="op">]</span> <span class="op">=</span> <span class="nam">target</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t99" href="#t99">99</a></span><span class="t"> <span class="key">if</span> <span class="nam">stop</span> <span class="key">is</span> <span class="key">not</span> <span class="key">None</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t100" href="#t100">100</a></span><span class="t"> <span class="nam">w</span><span class="op">[</span><span class="str">"stop"</span><span class="op">]</span> <span class="op">=</span> <span class="nam">stop</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t101" href="#t101">101</a></span><span class="t"> <span class="key">if</span> <span class="nam">notes</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t102" href="#t102">102</a></span><span class="t"> <span class="nam">w</span><span class="op">[</span><span class="str">"notes"</span><span class="op">]</span> <span class="op">=</span> <span class="nam">notes</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t103" href="#t103">103</a></span><span class="t"> <span class="key">if</span> <span class="nam">alerts</span> <span class="key">is</span> <span class="key">not</span> <span class="key">None</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t104" href="#t104">104</a></span><span class="t"> <span class="nam">w</span><span class="op">[</span><span class="str">"alerts"</span><span class="op">]</span> <span class="op">=</span> <span class="nam">alerts</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t105" href="#t105">105</a></span><span class="t"> <span class="nam">save_stocks</span><span class="op">(</span><span class="nam">data</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t106" href="#t106">106</a></span><span class="t"> <span class="key">return</span> <span class="key">True</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t107" href="#t107">107</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t108" href="#t108">108</a></span><span class="t"> <span class="com"># Add new</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t109" href="#t109">109</a></span><span class="t"> <span class="nam">data</span><span class="op">[</span><span class="str">"watchlist"</span><span class="op">]</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="op">{</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t110" href="#t110">110</a></span><span class="t"> <span class="str">"ticker"</span><span class="op">:</span> <span class="nam">ticker</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t111" href="#t111">111</a></span><span class="t"> <span class="str">"target"</span><span class="op">:</span> <span class="nam">target</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t112" href="#t112">112</a></span><span class="t"> <span class="str">"stop"</span><span class="op">:</span> <span class="nam">stop</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t113" href="#t113">113</a></span><span class="t"> <span class="str">"alerts"</span><span class="op">:</span> <span class="nam">alerts</span> <span class="key">or</span> <span class="op">[</span><span class="op">]</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t114" href="#t114">114</a></span><span class="t"> <span class="str">"notes"</span><span class="op">:</span> <span class="nam">notes</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t115" href="#t115">115</a></span><span class="t"> <span class="op">}</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t116" href="#t116">116</a></span><span class="t"> <span class="nam">data</span><span class="op">[</span><span class="str">"watchlist"</span><span class="op">]</span><span class="op">.</span><span class="nam">sort</span><span class="op">(</span><span class="nam">key</span><span class="op">=</span><span class="key">lambda</span> <span class="nam">x</span><span class="op">:</span> <span class="nam">x</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"ticker"</span><span class="op">,</span> <span class="str">""</span><span class="op">)</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t117" href="#t117">117</a></span><span class="t"> <span class="nam">save_stocks</span><span class="op">(</span><span class="nam">data</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t118" href="#t118">118</a></span><span class="t"> <span class="key">return</span> <span class="key">True</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t119" href="#t119">119</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t120" href="#t120">120</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t121" href="#t121">121</a></span><span class="t"><span class="key">def</span> <span class="nam">add_to_holdings</span><span class="op">(</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t122" href="#t122">122</a></span><span class="t"> <span class="nam">ticker</span><span class="op">:</span> <span class="nam">str</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t123" href="#t123">123</a></span><span class="t"> <span class="nam">name</span><span class="op">:</span> <span class="nam">str</span> <span class="op">=</span> <span class="str">""</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t124" href="#t124">124</a></span><span class="t"> <span class="nam">category</span><span class="op">:</span> <span class="nam">str</span> <span class="op">=</span> <span class="str">""</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t125" href="#t125">125</a></span><span class="t"> <span class="nam">notes</span><span class="op">:</span> <span class="nam">str</span> <span class="op">=</span> <span class="str">""</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t126" href="#t126">126</a></span><span class="t"> <span class="nam">target</span><span class="op">:</span> <span class="nam">Optional</span><span class="op">[</span><span class="nam">float</span><span class="op">]</span> <span class="op">=</span> <span class="key">None</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t127" href="#t127">127</a></span><span class="t"> <span class="nam">stop</span><span class="op">:</span> <span class="nam">Optional</span><span class="op">[</span><span class="nam">float</span><span class="op">]</span> <span class="op">=</span> <span class="key">None</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t128" href="#t128">128</a></span><span class="t"> <span class="nam">alerts</span><span class="op">:</span> <span class="nam">Optional</span><span class="op">[</span><span class="nam">list</span><span class="op">]</span> <span class="op">=</span> <span class="key">None</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t129" href="#t129">129</a></span><span class="t"><span class="op">)</span> <span class="op">-></span> <span class="nam">bool</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t130" href="#t130">130</a></span><span class="t"> <span class="str">"""Add a stock to holdings. Target/stop for 'buy more' alerts."""</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t131" href="#t131">131</a></span><span class="t"> <span class="nam">data</span> <span class="op">=</span> <span class="nam">load_stocks</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t132" href="#t132">132</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t133" href="#t133">133</a></span><span class="t"> <span class="com"># Check if already in holdings</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t134" href="#t134">134</a></span><span class="t"> <span class="key">for</span> <span class="nam">h</span> <span class="key">in</span> <span class="nam">data</span><span class="op">[</span><span class="str">"holdings"</span><span class="op">]</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t135" href="#t135">135</a></span><span class="t"> <span class="key">if</span> <span class="nam">h</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"ticker"</span><span class="op">)</span> <span class="op">==</span> <span class="nam">ticker</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t136" href="#t136">136</a></span><span class="t"> <span class="com"># Update existing</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t137" href="#t137">137</a></span><span class="t"> <span class="key">if</span> <span class="nam">name</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t138" href="#t138">138</a></span><span class="t"> <span class="nam">h</span><span class="op">[</span><span class="str">"name"</span><span class="op">]</span> <span class="op">=</span> <span class="nam">name</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t139" href="#t139">139</a></span><span class="t"> <span class="key">if</span> <span class="nam">category</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t140" href="#t140">140</a></span><span class="t"> <span class="nam">h</span><span class="op">[</span><span class="str">"category"</span><span class="op">]</span> <span class="op">=</span> <span class="nam">category</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t141" href="#t141">141</a></span><span class="t"> <span class="key">if</span> <span class="nam">notes</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t142" href="#t142">142</a></span><span class="t"> <span class="nam">h</span><span class="op">[</span><span class="str">"notes"</span><span class="op">]</span> <span class="op">=</span> <span class="nam">notes</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t143" href="#t143">143</a></span><span class="t"> <span class="key">if</span> <span class="nam">target</span> <span class="key">is</span> <span class="key">not</span> <span class="key">None</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t144" href="#t144">144</a></span><span class="t"> <span class="nam">h</span><span class="op">[</span><span class="str">"target"</span><span class="op">]</span> <span class="op">=</span> <span class="nam">target</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t145" href="#t145">145</a></span><span class="t"> <span class="key">if</span> <span class="nam">stop</span> <span class="key">is</span> <span class="key">not</span> <span class="key">None</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t146" href="#t146">146</a></span><span class="t"> <span class="nam">h</span><span class="op">[</span><span class="str">"stop"</span><span class="op">]</span> <span class="op">=</span> <span class="nam">stop</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t147" href="#t147">147</a></span><span class="t"> <span class="key">if</span> <span class="nam">alerts</span> <span class="key">is</span> <span class="key">not</span> <span class="key">None</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t148" href="#t148">148</a></span><span class="t"> <span class="nam">h</span><span class="op">[</span><span class="str">"alerts"</span><span class="op">]</span> <span class="op">=</span> <span class="nam">alerts</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t149" href="#t149">149</a></span><span class="t"> <span class="nam">save_stocks</span><span class="op">(</span><span class="nam">data</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t150" href="#t150">150</a></span><span class="t"> <span class="key">return</span> <span class="key">True</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t151" href="#t151">151</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t152" href="#t152">152</a></span><span class="t"> <span class="com"># Add new</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t153" href="#t153">153</a></span><span class="t"> <span class="nam">data</span><span class="op">[</span><span class="str">"holdings"</span><span class="op">]</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="op">{</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t154" href="#t154">154</a></span><span class="t"> <span class="str">"ticker"</span><span class="op">:</span> <span class="nam">ticker</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t155" href="#t155">155</a></span><span class="t"> <span class="str">"name"</span><span class="op">:</span> <span class="nam">name</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t156" href="#t156">156</a></span><span class="t"> <span class="str">"category"</span><span class="op">:</span> <span class="nam">category</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t157" href="#t157">157</a></span><span class="t"> <span class="str">"notes"</span><span class="op">:</span> <span class="nam">notes</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t158" href="#t158">158</a></span><span class="t"> <span class="str">"target"</span><span class="op">:</span> <span class="nam">target</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t159" href="#t159">159</a></span><span class="t"> <span class="str">"stop"</span><span class="op">:</span> <span class="nam">stop</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t160" href="#t160">160</a></span><span class="t"> <span class="str">"alerts"</span><span class="op">:</span> <span class="nam">alerts</span> <span class="key">or</span> <span class="op">[</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t161" href="#t161">161</a></span><span class="t"> <span class="op">}</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t162" href="#t162">162</a></span><span class="t"> <span class="nam">data</span><span class="op">[</span><span class="str">"holdings"</span><span class="op">]</span><span class="op">.</span><span class="nam">sort</span><span class="op">(</span><span class="nam">key</span><span class="op">=</span><span class="key">lambda</span> <span class="nam">x</span><span class="op">:</span> <span class="nam">x</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"ticker"</span><span class="op">,</span> <span class="str">""</span><span class="op">)</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t163" href="#t163">163</a></span><span class="t"> <span class="nam">save_stocks</span><span class="op">(</span><span class="nam">data</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t164" href="#t164">164</a></span><span class="t"> <span class="key">return</span> <span class="key">True</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t165" href="#t165">165</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t166" href="#t166">166</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t167" href="#t167">167</a></span><span class="t"><span class="key">def</span> <span class="nam">move_to_holdings</span><span class="op">(</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t168" href="#t168">168</a></span><span class="t"> <span class="nam">ticker</span><span class="op">:</span> <span class="nam">str</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t169" href="#t169">169</a></span><span class="t"> <span class="nam">name</span><span class="op">:</span> <span class="nam">str</span> <span class="op">=</span> <span class="str">""</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t170" href="#t170">170</a></span><span class="t"> <span class="nam">category</span><span class="op">:</span> <span class="nam">str</span> <span class="op">=</span> <span class="str">""</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t171" href="#t171">171</a></span><span class="t"> <span class="nam">notes</span><span class="op">:</span> <span class="nam">str</span> <span class="op">=</span> <span class="str">""</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t172" href="#t172">172</a></span><span class="t"><span class="op">)</span> <span class="op">-></span> <span class="nam">bool</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t173" href="#t173">173</a></span><span class="t"> <span class="str">"""Move a stock from watchlist to holdings (you bought it)."""</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t174" href="#t174">174</a></span><span class="t"> <span class="nam">data</span> <span class="op">=</span> <span class="nam">load_stocks</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t175" href="#t175">175</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t176" href="#t176">176</a></span><span class="t"> <span class="com"># Find in watchlist</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t177" href="#t177">177</a></span><span class="t"> <span class="nam">watchlist_item</span> <span class="op">=</span> <span class="key">None</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t178" href="#t178">178</a></span><span class="t"> <span class="key">for</span> <span class="nam">i</span><span class="op">,</span> <span class="nam">w</span> <span class="key">in</span> <span class="nam">enumerate</span><span class="op">(</span><span class="nam">data</span><span class="op">[</span><span class="str">"watchlist"</span><span class="op">]</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t179" href="#t179">179</a></span><span class="t"> <span class="key">if</span> <span class="nam">w</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"ticker"</span><span class="op">)</span> <span class="op">==</span> <span class="nam">ticker</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t180" href="#t180">180</a></span><span class="t"> <span class="nam">watchlist_item</span> <span class="op">=</span> <span class="nam">data</span><span class="op">[</span><span class="str">"watchlist"</span><span class="op">]</span><span class="op">.</span><span class="nam">pop</span><span class="op">(</span><span class="nam">i</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t181" href="#t181">181</a></span><span class="t"> <span class="key">break</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t182" href="#t182">182</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t183" href="#t183">183</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">watchlist_item</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t184" href="#t184">184</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"⚠️ {ticker} not found in watchlist"</span><span class="op">,</span> <span class="nam">file</span><span class="op">=</span><span class="nam">sys</span><span class="op">.</span><span class="nam">stderr</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t185" href="#t185">185</a></span><span class="t"> <span class="key">return</span> <span class="key">False</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t186" href="#t186">186</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t187" href="#t187">187</a></span><span class="t"> <span class="com"># Add to holdings</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t188" href="#t188">188</a></span><span class="t"> <span class="nam">data</span><span class="op">[</span><span class="str">"holdings"</span><span class="op">]</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="op">{</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t189" href="#t189">189</a></span><span class="t"> <span class="str">"ticker"</span><span class="op">:</span> <span class="nam">ticker</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t190" href="#t190">190</a></span><span class="t"> <span class="str">"name"</span><span class="op">:</span> <span class="nam">name</span> <span class="key">or</span> <span class="nam">watchlist_item</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"notes"</span><span class="op">,</span> <span class="str">""</span><span class="op">)</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t191" href="#t191">191</a></span><span class="t"> <span class="str">"category"</span><span class="op">:</span> <span class="nam">category</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t192" href="#t192">192</a></span><span class="t"> <span class="str">"notes"</span><span class="op">:</span> <span class="nam">notes</span> <span class="key">or</span> <span class="str">f"Bought (was on watchlist with target ${watchlist_item.get('target', 'N/A')})"</span> </span><span class="r"></span></p>
|
||||
<p class="run run2"><span class="n"><a id="t193" href="#t193">193</a></span><span class="t"> <span class="op">}</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t194" href="#t194">194</a></span><span class="t"> <span class="nam">data</span><span class="op">[</span><span class="str">"holdings"</span><span class="op">]</span><span class="op">.</span><span class="nam">sort</span><span class="op">(</span><span class="nam">key</span><span class="op">=</span><span class="key">lambda</span> <span class="nam">x</span><span class="op">:</span> <span class="nam">x</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"ticker"</span><span class="op">,</span> <span class="str">""</span><span class="op">)</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t195" href="#t195">195</a></span><span class="t"> <span class="nam">save_stocks</span><span class="op">(</span><span class="nam">data</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t196" href="#t196">196</a></span><span class="t"> <span class="key">return</span> <span class="key">True</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t197" href="#t197">197</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t198" href="#t198">198</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t199" href="#t199">199</a></span><span class="t"><span class="key">def</span> <span class="nam">remove_stock</span><span class="op">(</span><span class="nam">ticker</span><span class="op">:</span> <span class="nam">str</span><span class="op">,</span> <span class="nam">from_list</span><span class="op">:</span> <span class="nam">str</span> <span class="op">=</span> <span class="str">"both"</span><span class="op">)</span> <span class="op">-></span> <span class="nam">bool</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t200" href="#t200">200</a></span><span class="t"> <span class="str">"""Remove a stock from holdings, watchlist, or both."""</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t201" href="#t201">201</a></span><span class="t"> <span class="nam">data</span> <span class="op">=</span> <span class="nam">load_stocks</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t202" href="#t202">202</a></span><span class="t"> <span class="nam">removed</span> <span class="op">=</span> <span class="key">False</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t203" href="#t203">203</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t204" href="#t204">204</a></span><span class="t"> <span class="key">if</span> <span class="nam">from_list</span> <span class="key">in</span> <span class="op">(</span><span class="str">"holdings"</span><span class="op">,</span> <span class="str">"both"</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t205" href="#t205">205</a></span><span class="t"> <span class="nam">original_len</span> <span class="op">=</span> <span class="nam">len</span><span class="op">(</span><span class="nam">data</span><span class="op">[</span><span class="str">"holdings"</span><span class="op">]</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t206" href="#t206">206</a></span><span class="t"> <span class="nam">data</span><span class="op">[</span><span class="str">"holdings"</span><span class="op">]</span> <span class="op">=</span> <span class="op">[</span><span class="nam">h</span> <span class="key">for</span> <span class="nam">h</span> <span class="key">in</span> <span class="nam">data</span><span class="op">[</span><span class="str">"holdings"</span><span class="op">]</span> <span class="key">if</span> <span class="nam">h</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"ticker"</span><span class="op">)</span> <span class="op">!=</span> <span class="nam">ticker</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t207" href="#t207">207</a></span><span class="t"> <span class="key">if</span> <span class="nam">len</span><span class="op">(</span><span class="nam">data</span><span class="op">[</span><span class="str">"holdings"</span><span class="op">]</span><span class="op">)</span> <span class="op"><</span> <span class="nam">original_len</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t208" href="#t208">208</a></span><span class="t"> <span class="nam">removed</span> <span class="op">=</span> <span class="key">True</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t209" href="#t209">209</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t210" href="#t210">210</a></span><span class="t"> <span class="key">if</span> <span class="nam">from_list</span> <span class="key">in</span> <span class="op">(</span><span class="str">"watchlist"</span><span class="op">,</span> <span class="str">"both"</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t211" href="#t211">211</a></span><span class="t"> <span class="nam">original_len</span> <span class="op">=</span> <span class="nam">len</span><span class="op">(</span><span class="nam">data</span><span class="op">[</span><span class="str">"watchlist"</span><span class="op">]</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t212" href="#t212">212</a></span><span class="t"> <span class="nam">data</span><span class="op">[</span><span class="str">"watchlist"</span><span class="op">]</span> <span class="op">=</span> <span class="op">[</span><span class="nam">w</span> <span class="key">for</span> <span class="nam">w</span> <span class="key">in</span> <span class="nam">data</span><span class="op">[</span><span class="str">"watchlist"</span><span class="op">]</span> <span class="key">if</span> <span class="nam">w</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"ticker"</span><span class="op">)</span> <span class="op">!=</span> <span class="nam">ticker</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t213" href="#t213">213</a></span><span class="t"> <span class="key">if</span> <span class="nam">len</span><span class="op">(</span><span class="nam">data</span><span class="op">[</span><span class="str">"watchlist"</span><span class="op">]</span><span class="op">)</span> <span class="op"><</span> <span class="nam">original_len</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t214" href="#t214">214</a></span><span class="t"> <span class="nam">removed</span> <span class="op">=</span> <span class="key">True</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t215" href="#t215">215</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t216" href="#t216">216</a></span><span class="t"> <span class="key">if</span> <span class="nam">removed</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t217" href="#t217">217</a></span><span class="t"> <span class="nam">save_stocks</span><span class="op">(</span><span class="nam">data</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t218" href="#t218">218</a></span><span class="t"> <span class="key">return</span> <span class="nam">removed</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t219" href="#t219">219</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t220" href="#t220">220</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t221" href="#t221">221</a></span><span class="t"><span class="key">def</span> <span class="nam">list_stocks</span><span class="op">(</span><span class="nam">show_holdings</span><span class="op">:</span> <span class="nam">bool</span> <span class="op">=</span> <span class="key">True</span><span class="op">,</span> <span class="nam">show_watchlist</span><span class="op">:</span> <span class="nam">bool</span> <span class="op">=</span> <span class="key">True</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t222" href="#t222">222</a></span><span class="t"> <span class="str">"""Print stocks list."""</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t223" href="#t223">223</a></span><span class="t"> <span class="nam">data</span> <span class="op">=</span> <span class="nam">load_stocks</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t224" href="#t224">224</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t225" href="#t225">225</a></span><span class="t"> <span class="key">if</span> <span class="nam">show_holdings</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t226" href="#t226">226</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"\n📊 HOLDINGS ({len(data['holdings'])})"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t227" href="#t227">227</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">"-"</span> <span class="op">*</span> <span class="num">50</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t228" href="#t228">228</a></span><span class="t"> <span class="key">for</span> <span class="nam">h</span> <span class="key">in</span> <span class="nam">data</span><span class="op">[</span><span class="str">"holdings"</span><span class="op">]</span><span class="op">[</span><span class="op">:</span><span class="num">20</span><span class="op">]</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t229" href="#t229">229</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f" {h['ticker']:10} {h.get('name', '')[:30]}"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t230" href="#t230">230</a></span><span class="t"> <span class="key">if</span> <span class="nam">len</span><span class="op">(</span><span class="nam">data</span><span class="op">[</span><span class="str">"holdings"</span><span class="op">]</span><span class="op">)</span> <span class="op">></span> <span class="num">20</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t231" href="#t231">231</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f" ... and {len(data['holdings']) - 20} more"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t232" href="#t232">232</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t233" href="#t233">233</a></span><span class="t"> <span class="key">if</span> <span class="nam">show_watchlist</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t234" href="#t234">234</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"\n👀 WATCHLIST ({len(data['watchlist'])})"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t235" href="#t235">235</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">"-"</span> <span class="op">*</span> <span class="num">50</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t236" href="#t236">236</a></span><span class="t"> <span class="key">for</span> <span class="nam">w</span> <span class="key">in</span> <span class="nam">data</span><span class="op">[</span><span class="str">"watchlist"</span><span class="op">]</span><span class="op">[</span><span class="op">:</span><span class="num">20</span><span class="op">]</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t237" href="#t237">237</a></span><span class="t"> <span class="nam">target</span> <span class="op">=</span> <span class="str">f"${w['target']}"</span> <span class="key">if</span> <span class="nam">w</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'target'</span><span class="op">)</span> <span class="key">else</span> <span class="str">"no target"</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t238" href="#t238">238</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f" {w['ticker']:10} {target:>10} {w.get('notes', '')[:25]}"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t239" href="#t239">239</a></span><span class="t"> <span class="key">if</span> <span class="nam">len</span><span class="op">(</span><span class="nam">data</span><span class="op">[</span><span class="str">"watchlist"</span><span class="op">]</span><span class="op">)</span> <span class="op">></span> <span class="num">20</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t240" href="#t240">240</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f" ... and {len(data['watchlist']) - 20} more"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t241" href="#t241">241</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t242" href="#t242">242</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t243" href="#t243">243</a></span><span class="t"><span class="key">def</span> <span class="nam">main</span><span class="op">(</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t244" href="#t244">244</a></span><span class="t"> <span class="nam">parser</span> <span class="op">=</span> <span class="nam">argparse</span><span class="op">.</span><span class="nam">ArgumentParser</span><span class="op">(</span><span class="nam">description</span><span class="op">=</span><span class="str">"Unified stock management"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t245" href="#t245">245</a></span><span class="t"> <span class="nam">subparsers</span> <span class="op">=</span> <span class="nam">parser</span><span class="op">.</span><span class="nam">add_subparsers</span><span class="op">(</span><span class="nam">dest</span><span class="op">=</span><span class="str">"command"</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"Commands"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t246" href="#t246">246</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t247" href="#t247">247</a></span><span class="t"> <span class="com"># list</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t248" href="#t248">248</a></span><span class="t"> <span class="nam">list_parser</span> <span class="op">=</span> <span class="nam">subparsers</span><span class="op">.</span><span class="nam">add_parser</span><span class="op">(</span><span class="str">"list"</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"List stocks"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t249" href="#t249">249</a></span><span class="t"> <span class="nam">list_parser</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">"--holdings"</span><span class="op">,</span> <span class="nam">action</span><span class="op">=</span><span class="str">"store_true"</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"Show only holdings"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t250" href="#t250">250</a></span><span class="t"> <span class="nam">list_parser</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">"--watchlist"</span><span class="op">,</span> <span class="nam">action</span><span class="op">=</span><span class="str">"store_true"</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"Show only watchlist"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t251" href="#t251">251</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t252" href="#t252">252</a></span><span class="t"> <span class="com"># add-watchlist</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t253" href="#t253">253</a></span><span class="t"> <span class="nam">add_watch</span> <span class="op">=</span> <span class="nam">subparsers</span><span class="op">.</span><span class="nam">add_parser</span><span class="op">(</span><span class="str">"add-watchlist"</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"Add to watchlist"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t254" href="#t254">254</a></span><span class="t"> <span class="nam">add_watch</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">"ticker"</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"Stock ticker"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t255" href="#t255">255</a></span><span class="t"> <span class="nam">add_watch</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">"--target"</span><span class="op">,</span> <span class="nam">type</span><span class="op">=</span><span class="nam">float</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"Target price"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t256" href="#t256">256</a></span><span class="t"> <span class="nam">add_watch</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">"--stop"</span><span class="op">,</span> <span class="nam">type</span><span class="op">=</span><span class="nam">float</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"Stop loss"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t257" href="#t257">257</a></span><span class="t"> <span class="nam">add_watch</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">"--notes"</span><span class="op">,</span> <span class="nam">default</span><span class="op">=</span><span class="str">""</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"Notes"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t258" href="#t258">258</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t259" href="#t259">259</a></span><span class="t"> <span class="com"># add-holding</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t260" href="#t260">260</a></span><span class="t"> <span class="nam">add_hold</span> <span class="op">=</span> <span class="nam">subparsers</span><span class="op">.</span><span class="nam">add_parser</span><span class="op">(</span><span class="str">"add-holding"</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"Add to holdings"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t261" href="#t261">261</a></span><span class="t"> <span class="nam">add_hold</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">"ticker"</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"Stock ticker"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t262" href="#t262">262</a></span><span class="t"> <span class="nam">add_hold</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">"--name"</span><span class="op">,</span> <span class="nam">default</span><span class="op">=</span><span class="str">""</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"Company name"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t263" href="#t263">263</a></span><span class="t"> <span class="nam">add_hold</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">"--category"</span><span class="op">,</span> <span class="nam">default</span><span class="op">=</span><span class="str">""</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"Category"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t264" href="#t264">264</a></span><span class="t"> <span class="nam">add_hold</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">"--notes"</span><span class="op">,</span> <span class="nam">default</span><span class="op">=</span><span class="str">""</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"Notes"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t265" href="#t265">265</a></span><span class="t"> <span class="nam">add_hold</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">"--target"</span><span class="op">,</span> <span class="nam">type</span><span class="op">=</span><span class="nam">float</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"Buy-more target price"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t266" href="#t266">266</a></span><span class="t"> <span class="nam">add_hold</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">"--stop"</span><span class="op">,</span> <span class="nam">type</span><span class="op">=</span><span class="nam">float</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"Stop loss price"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t267" href="#t267">267</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t268" href="#t268">268</a></span><span class="t"> <span class="com"># move (watchlist → holdings)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t269" href="#t269">269</a></span><span class="t"> <span class="nam">move</span> <span class="op">=</span> <span class="nam">subparsers</span><span class="op">.</span><span class="nam">add_parser</span><span class="op">(</span><span class="str">"move"</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"Move from watchlist to holdings"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t270" href="#t270">270</a></span><span class="t"> <span class="nam">move</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">"ticker"</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"Stock ticker"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t271" href="#t271">271</a></span><span class="t"> <span class="nam">move</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">"--name"</span><span class="op">,</span> <span class="nam">default</span><span class="op">=</span><span class="str">""</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"Company name"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t272" href="#t272">272</a></span><span class="t"> <span class="nam">move</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">"--category"</span><span class="op">,</span> <span class="nam">default</span><span class="op">=</span><span class="str">""</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"Category"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t273" href="#t273">273</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t274" href="#t274">274</a></span><span class="t"> <span class="com"># remove</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t275" href="#t275">275</a></span><span class="t"> <span class="nam">remove</span> <span class="op">=</span> <span class="nam">subparsers</span><span class="op">.</span><span class="nam">add_parser</span><span class="op">(</span><span class="str">"remove"</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"Remove stock"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t276" href="#t276">276</a></span><span class="t"> <span class="nam">remove</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">"ticker"</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"Stock ticker"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t277" href="#t277">277</a></span><span class="t"> <span class="nam">remove</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">"--from"</span><span class="op">,</span> <span class="nam">dest</span><span class="op">=</span><span class="str">"from_list"</span><span class="op">,</span> <span class="nam">choices</span><span class="op">=</span><span class="op">[</span><span class="str">"holdings"</span><span class="op">,</span> <span class="str">"watchlist"</span><span class="op">,</span> <span class="str">"both"</span><span class="op">]</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t278" href="#t278">278</a></span><span class="t"> <span class="nam">default</span><span class="op">=</span><span class="str">"both"</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"Remove from which list"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t279" href="#t279">279</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t280" href="#t280">280</a></span><span class="t"> <span class="com"># set-alert (for existing holdings)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t281" href="#t281">281</a></span><span class="t"> <span class="nam">set_alert</span> <span class="op">=</span> <span class="nam">subparsers</span><span class="op">.</span><span class="nam">add_parser</span><span class="op">(</span><span class="str">"set-alert"</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"Set buy-more/stop alert on holding"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t282" href="#t282">282</a></span><span class="t"> <span class="nam">set_alert</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">"ticker"</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"Stock ticker"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t283" href="#t283">283</a></span><span class="t"> <span class="nam">set_alert</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">"--target"</span><span class="op">,</span> <span class="nam">type</span><span class="op">=</span><span class="nam">float</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"Buy-more target price"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t284" href="#t284">284</a></span><span class="t"> <span class="nam">set_alert</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">"--stop"</span><span class="op">,</span> <span class="nam">type</span><span class="op">=</span><span class="nam">float</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">"Stop loss price"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t285" href="#t285">285</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t286" href="#t286">286</a></span><span class="t"> <span class="nam">args</span> <span class="op">=</span> <span class="nam">parser</span><span class="op">.</span><span class="nam">parse_args</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t287" href="#t287">287</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t288" href="#t288">288</a></span><span class="t"> <span class="key">if</span> <span class="nam">args</span><span class="op">.</span><span class="nam">command</span> <span class="op">==</span> <span class="str">"list"</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t289" href="#t289">289</a></span><span class="t"> <span class="nam">show_h</span> <span class="op">=</span> <span class="key">not</span> <span class="nam">args</span><span class="op">.</span><span class="nam">watchlist</span> <span class="key">or</span> <span class="nam">args</span><span class="op">.</span><span class="nam">holdings</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t290" href="#t290">290</a></span><span class="t"> <span class="nam">show_w</span> <span class="op">=</span> <span class="key">not</span> <span class="nam">args</span><span class="op">.</span><span class="nam">holdings</span> <span class="key">or</span> <span class="nam">args</span><span class="op">.</span><span class="nam">watchlist</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t291" href="#t291">291</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">args</span><span class="op">.</span><span class="nam">holdings</span> <span class="key">and</span> <span class="key">not</span> <span class="nam">args</span><span class="op">.</span><span class="nam">watchlist</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t292" href="#t292">292</a></span><span class="t"> <span class="nam">show_h</span> <span class="op">=</span> <span class="nam">show_w</span> <span class="op">=</span> <span class="key">True</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t293" href="#t293">293</a></span><span class="t"> <span class="nam">list_stocks</span><span class="op">(</span><span class="nam">show_holdings</span><span class="op">=</span><span class="nam">show_h</span><span class="op">,</span> <span class="nam">show_watchlist</span><span class="op">=</span><span class="nam">show_w</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t294" href="#t294">294</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t295" href="#t295">295</a></span><span class="t"> <span class="key">elif</span> <span class="nam">args</span><span class="op">.</span><span class="nam">command</span> <span class="op">==</span> <span class="str">"add-watchlist"</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t296" href="#t296">296</a></span><span class="t"> <span class="nam">add_to_watchlist</span><span class="op">(</span><span class="nam">args</span><span class="op">.</span><span class="nam">ticker</span><span class="op">.</span><span class="nam">upper</span><span class="op">(</span><span class="op">)</span><span class="op">,</span> <span class="nam">args</span><span class="op">.</span><span class="nam">target</span><span class="op">,</span> <span class="nam">args</span><span class="op">.</span><span class="nam">stop</span><span class="op">,</span> <span class="nam">args</span><span class="op">.</span><span class="nam">notes</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t297" href="#t297">297</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"✅ Added {args.ticker.upper()} to watchlist"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t298" href="#t298">298</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t299" href="#t299">299</a></span><span class="t"> <span class="key">elif</span> <span class="nam">args</span><span class="op">.</span><span class="nam">command</span> <span class="op">==</span> <span class="str">"add-holding"</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t300" href="#t300">300</a></span><span class="t"> <span class="nam">add_to_holdings</span><span class="op">(</span><span class="nam">args</span><span class="op">.</span><span class="nam">ticker</span><span class="op">.</span><span class="nam">upper</span><span class="op">(</span><span class="op">)</span><span class="op">,</span> <span class="nam">args</span><span class="op">.</span><span class="nam">name</span><span class="op">,</span> <span class="nam">args</span><span class="op">.</span><span class="nam">category</span><span class="op">,</span> <span class="nam">args</span><span class="op">.</span><span class="nam">notes</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t301" href="#t301">301</a></span><span class="t"> <span class="nam">args</span><span class="op">.</span><span class="nam">target</span><span class="op">,</span> <span class="nam">args</span><span class="op">.</span><span class="nam">stop</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t302" href="#t302">302</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"✅ Added {args.ticker.upper()} to holdings"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t303" href="#t303">303</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t304" href="#t304">304</a></span><span class="t"> <span class="key">elif</span> <span class="nam">args</span><span class="op">.</span><span class="nam">command</span> <span class="op">==</span> <span class="str">"move"</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t305" href="#t305">305</a></span><span class="t"> <span class="key">if</span> <span class="nam">move_to_holdings</span><span class="op">(</span><span class="nam">args</span><span class="op">.</span><span class="nam">ticker</span><span class="op">.</span><span class="nam">upper</span><span class="op">(</span><span class="op">)</span><span class="op">,</span> <span class="nam">args</span><span class="op">.</span><span class="nam">name</span><span class="op">,</span> <span class="nam">args</span><span class="op">.</span><span class="nam">category</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t306" href="#t306">306</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"✅ Moved {args.ticker.upper()} from watchlist to holdings"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t307" href="#t307">307</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t308" href="#t308">308</a></span><span class="t"> <span class="key">elif</span> <span class="nam">args</span><span class="op">.</span><span class="nam">command</span> <span class="op">==</span> <span class="str">"remove"</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t309" href="#t309">309</a></span><span class="t"> <span class="key">if</span> <span class="nam">remove_stock</span><span class="op">(</span><span class="nam">args</span><span class="op">.</span><span class="nam">ticker</span><span class="op">.</span><span class="nam">upper</span><span class="op">(</span><span class="op">)</span><span class="op">,</span> <span class="nam">args</span><span class="op">.</span><span class="nam">from_list</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t310" href="#t310">310</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"✅ Removed {args.ticker.upper()}"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t311" href="#t311">311</a></span><span class="t"> <span class="key">else</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t312" href="#t312">312</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"⚠️ {args.ticker.upper()} not found"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t313" href="#t313">313</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t314" href="#t314">314</a></span><span class="t"> <span class="key">elif</span> <span class="nam">args</span><span class="op">.</span><span class="nam">command</span> <span class="op">==</span> <span class="str">"set-alert"</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t315" href="#t315">315</a></span><span class="t"> <span class="nam">data</span> <span class="op">=</span> <span class="nam">load_stocks</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t316" href="#t316">316</a></span><span class="t"> <span class="nam">found</span> <span class="op">=</span> <span class="key">False</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t317" href="#t317">317</a></span><span class="t"> <span class="key">for</span> <span class="nam">h</span> <span class="key">in</span> <span class="nam">data</span><span class="op">[</span><span class="str">"holdings"</span><span class="op">]</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t318" href="#t318">318</a></span><span class="t"> <span class="key">if</span> <span class="nam">h</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"ticker"</span><span class="op">)</span> <span class="op">==</span> <span class="nam">args</span><span class="op">.</span><span class="nam">ticker</span><span class="op">.</span><span class="nam">upper</span><span class="op">(</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t319" href="#t319">319</a></span><span class="t"> <span class="key">if</span> <span class="nam">args</span><span class="op">.</span><span class="nam">target</span> <span class="key">is</span> <span class="key">not</span> <span class="key">None</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t320" href="#t320">320</a></span><span class="t"> <span class="nam">h</span><span class="op">[</span><span class="str">"target"</span><span class="op">]</span> <span class="op">=</span> <span class="nam">args</span><span class="op">.</span><span class="nam">target</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t321" href="#t321">321</a></span><span class="t"> <span class="key">if</span> <span class="nam">args</span><span class="op">.</span><span class="nam">stop</span> <span class="key">is</span> <span class="key">not</span> <span class="key">None</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t322" href="#t322">322</a></span><span class="t"> <span class="nam">h</span><span class="op">[</span><span class="str">"stop"</span><span class="op">]</span> <span class="op">=</span> <span class="nam">args</span><span class="op">.</span><span class="nam">stop</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t323" href="#t323">323</a></span><span class="t"> <span class="nam">save_stocks</span><span class="op">(</span><span class="nam">data</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t324" href="#t324">324</a></span><span class="t"> <span class="nam">found</span> <span class="op">=</span> <span class="key">True</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t325" href="#t325">325</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"✅ Set alert on {args.ticker.upper()}: target=${args.target}, stop=${args.stop}"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t326" href="#t326">326</a></span><span class="t"> <span class="key">break</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t327" href="#t327">327</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">found</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t328" href="#t328">328</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"⚠️ {args.ticker.upper()} not found in holdings"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t329" href="#t329">329</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t330" href="#t330">330</a></span><span class="t"> <span class="key">else</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t331" href="#t331">331</a></span><span class="t"> <span class="nam">parser</span><span class="op">.</span><span class="nam">print_help</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t332" href="#t332">332</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t333" href="#t333">333</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="exc show_exc"><span class="n"><a id="t334" href="#t334">334</a></span><span class="t"><span class="key">if</span> <span class="nam">__name__</span> <span class="op">==</span> <span class="str">"__main__"</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="exc show_exc"><span class="n"><a id="t335" href="#t335">335</a></span><span class="t"> <span class="nam">main</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
</main>
|
||||
<footer>
|
||||
<div class="content">
|
||||
<p>
|
||||
<a class="nav" href="z_de1a740d5dc98ffd_setup_py.html">« prev</a>
|
||||
<a class="nav" href="index.html">^ index</a>
|
||||
<a class="nav" href="z_de1a740d5dc98ffd_summarize_py.html">» next</a>
|
||||
|
||||
<a class="nav" href="https://coverage.readthedocs.io/en/7.13.2">coverage.py v7.13.2</a>,
|
||||
created at 2026-02-01 16:34 -0800
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
1825
htmlcov/z_de1a740d5dc98ffd_summarize_py.html
generated
Normal file
1825
htmlcov/z_de1a740d5dc98ffd_summarize_py.html
generated
Normal file
File diff suppressed because it is too large
Load Diff
255
htmlcov/z_de1a740d5dc98ffd_translate_portfolio_py.html
generated
Normal file
255
htmlcov/z_de1a740d5dc98ffd_translate_portfolio_py.html
generated
Normal file
@@ -0,0 +1,255 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>Coverage for scripts/translate_portfolio.py: 0%</title>
|
||||
<link rel="icon" sizes="32x32" href="favicon_32_cb_c827f16f.png">
|
||||
<link rel="stylesheet" href="style_cb_9ff733b0.css" type="text/css">
|
||||
<script src="coverage_html_cb_dd2e7eb5.js" defer></script>
|
||||
</head>
|
||||
<body class="pyfile">
|
||||
<header>
|
||||
<div class="content">
|
||||
<h1>
|
||||
<span class="text">Coverage for </span><b>scripts / translate_portfolio.py</b>:
|
||||
<span class="pc_cov">0%</span>
|
||||
</h1>
|
||||
<aside id="help_panel_wrapper">
|
||||
<input id="help_panel_state" type="checkbox">
|
||||
<label for="help_panel_state">
|
||||
<img id="keyboard_icon" src="keybd_closed_cb_900cfef5.png" alt="Show/hide keyboard shortcuts">
|
||||
</label>
|
||||
<div id="help_panel">
|
||||
<p class="legend">Shortcuts on this page</p>
|
||||
<div class="keyhelp">
|
||||
<p>
|
||||
<kbd>r</kbd>
|
||||
<kbd>m</kbd>
|
||||
<kbd>x</kbd>
|
||||
toggle line displays
|
||||
</p>
|
||||
<p>
|
||||
<kbd>j</kbd>
|
||||
<kbd>k</kbd>
|
||||
next/prev highlighted chunk
|
||||
</p>
|
||||
<p>
|
||||
<kbd>0</kbd> (zero) top of page
|
||||
</p>
|
||||
<p>
|
||||
<kbd>1</kbd> (one) first highlighted chunk
|
||||
</p>
|
||||
<p>
|
||||
<kbd>[</kbd>
|
||||
<kbd>]</kbd>
|
||||
prev/next file
|
||||
</p>
|
||||
<p>
|
||||
<kbd>u</kbd> up to the index
|
||||
</p>
|
||||
<p>
|
||||
<kbd>?</kbd> show/hide this help
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
<h2>
|
||||
<span class="text">88 statements </span>
|
||||
<button type="button" class="run button_toggle_run" value="run" data-shortcut="r" title="Toggle lines run">0<span class="text"> run</span></button>
|
||||
<button type="button" class="mis show_mis button_toggle_mis" value="mis" data-shortcut="m" title="Toggle lines missing">88<span class="text"> missing</span></button>
|
||||
<button type="button" class="exc show_exc button_toggle_exc" value="exc" data-shortcut="x" title="Toggle lines excluded">2<span class="text"> excluded</span></button>
|
||||
</h2>
|
||||
<p class="text">
|
||||
<a id="prevFileLink" class="nav" href="z_de1a740d5dc98ffd_summarize_py.html">« prev</a>
|
||||
<a id="indexLink" class="nav" href="index.html">^ index</a>
|
||||
<a id="nextFileLink" class="nav" href="z_de1a740d5dc98ffd_utils_py.html">» next</a>
|
||||
|
||||
<a class="nav" href="https://coverage.readthedocs.io/en/7.13.2">coverage.py v7.13.2</a>,
|
||||
created at 2026-02-01 16:34 -0800
|
||||
</p>
|
||||
<aside class="hidden">
|
||||
<button type="button" class="button_next_chunk" data-shortcut="j"></button>
|
||||
<button type="button" class="button_prev_chunk" data-shortcut="k"></button>
|
||||
<button type="button" class="button_top_of_page" data-shortcut="0"></button>
|
||||
<button type="button" class="button_first_chunk" data-shortcut="1"></button>
|
||||
<button type="button" class="button_prev_file" data-shortcut="["></button>
|
||||
<button type="button" class="button_next_file" data-shortcut="]"></button>
|
||||
<button type="button" class="button_to_index" data-shortcut="u"></button>
|
||||
<button type="button" class="button_show_hide_help" data-shortcut="?"></button>
|
||||
</aside>
|
||||
</div>
|
||||
</header>
|
||||
<main id="source">
|
||||
<p class="pln"><span class="n"><a id="t1" href="#t1">1</a></span><span class="t"><span class="com">#!/usr/bin/env python3</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t2" href="#t2">2</a></span><span class="t"><span class="str">"""Translate portfolio headlines in briefing JSON using openclaw.</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t3" href="#t3">3</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t4" href="#t4">4</a></span><span class="t"><span class="str">Usage: python3 translate_portfolio.py /path/to/briefing.json [--lang de]</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t5" href="#t5">5</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t6" href="#t6">6</a></span><span class="t"><span class="str">Reads briefing JSON, translates portfolio article headlines via openclaw,</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t7" href="#t7">7</a></span><span class="t"><span class="str">writes back the modified JSON.</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t8" href="#t8">8</a></span><span class="t"><span class="str">"""</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t9" href="#t9">9</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t10" href="#t10">10</a></span><span class="t"><span class="key">import</span> <span class="nam">argparse</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t11" href="#t11">11</a></span><span class="t"><span class="key">import</span> <span class="nam">json</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t12" href="#t12">12</a></span><span class="t"><span class="key">import</span> <span class="nam">re</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t13" href="#t13">13</a></span><span class="t"><span class="key">import</span> <span class="nam">subprocess</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t14" href="#t14">14</a></span><span class="t"><span class="key">import</span> <span class="nam">sys</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t15" href="#t15">15</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t16" href="#t16">16</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t17" href="#t17">17</a></span><span class="t"><span class="key">def</span> <span class="nam">extract_headlines</span><span class="op">(</span><span class="nam">portfolio_message</span><span class="op">:</span> <span class="nam">str</span><span class="op">)</span> <span class="op">-></span> <span class="nam">list</span><span class="op">[</span><span class="nam">str</span><span class="op">]</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t18" href="#t18">18</a></span><span class="t"> <span class="str">"""Extract article headlines (lines starting with •) from portfolio message."""</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t19" href="#t19">19</a></span><span class="t"> <span class="nam">headlines</span> <span class="op">=</span> <span class="op">[</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t20" href="#t20">20</a></span><span class="t"> <span class="key">for</span> <span class="nam">line</span> <span class="key">in</span> <span class="nam">portfolio_message</span><span class="op">.</span><span class="nam">split</span><span class="op">(</span><span class="str">'\n'</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t21" href="#t21">21</a></span><span class="t"> <span class="nam">line</span> <span class="op">=</span> <span class="nam">line</span><span class="op">.</span><span class="nam">strip</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t22" href="#t22">22</a></span><span class="t"> <span class="key">if</span> <span class="nam">line</span><span class="op">.</span><span class="nam">startswith</span><span class="op">(</span><span class="str">'•'</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t23" href="#t23">23</a></span><span class="t"> <span class="com"># Remove bullet, reference number, and clean up</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t24" href="#t24">24</a></span><span class="t"> <span class="com"># Format: "• Headline text [1]"</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t25" href="#t25">25</a></span><span class="t"> <span class="nam">match</span> <span class="op">=</span> <span class="nam">re</span><span class="op">.</span><span class="nam">match</span><span class="op">(</span><span class="str">r'•\s*(.+?)\s*\[\d+\]$'</span><span class="op">,</span> <span class="nam">line</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t26" href="#t26">26</a></span><span class="t"> <span class="key">if</span> <span class="nam">match</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t27" href="#t27">27</a></span><span class="t"> <span class="nam">headlines</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="nam">match</span><span class="op">.</span><span class="nam">group</span><span class="op">(</span><span class="num">1</span><span class="op">)</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t28" href="#t28">28</a></span><span class="t"> <span class="key">else</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t29" href="#t29">29</a></span><span class="t"> <span class="com"># No reference number</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t30" href="#t30">30</a></span><span class="t"> <span class="nam">headlines</span><span class="op">.</span><span class="nam">append</span><span class="op">(</span><span class="nam">line</span><span class="op">[</span><span class="num">1</span><span class="op">:</span><span class="op">]</span><span class="op">.</span><span class="nam">strip</span><span class="op">(</span><span class="op">)</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t31" href="#t31">31</a></span><span class="t"> <span class="key">return</span> <span class="nam">headlines</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t32" href="#t32">32</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t33" href="#t33">33</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t34" href="#t34">34</a></span><span class="t"><span class="key">def</span> <span class="nam">translate_headlines</span><span class="op">(</span><span class="nam">headlines</span><span class="op">:</span> <span class="nam">list</span><span class="op">[</span><span class="nam">str</span><span class="op">]</span><span class="op">,</span> <span class="nam">lang</span><span class="op">:</span> <span class="nam">str</span> <span class="op">=</span> <span class="str">"de"</span><span class="op">)</span> <span class="op">-></span> <span class="nam">list</span><span class="op">[</span><span class="nam">str</span><span class="op">]</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t35" href="#t35">35</a></span><span class="t"> <span class="str">"""Translate headlines using openclaw agent."""</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t36" href="#t36">36</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">headlines</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t37" href="#t37">37</a></span><span class="t"> <span class="key">return</span> <span class="op">[</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t38" href="#t38">38</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t39" href="#t39">39</a></span><span class="t"> <span class="nam">prompt</span> <span class="op">=</span> <span class="str">f"""Translate these English headlines to German.</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t40" href="#t40">40</a></span><span class="t"><span class="str">Return ONLY a JSON array of strings in the same order.</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t41" href="#t41">41</a></span><span class="t"><span class="str">Example: ["Übersetzung 1", "Übersetzung 2"]</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t42" href="#t42">42</a></span><span class="t"><span class="str">Do not add commentary.</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t43" href="#t43">43</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t44" href="#t44">44</a></span><span class="t"><span class="str">Headlines:</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t45" href="#t45">45</a></span><span class="t"><span class="str">"""</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t46" href="#t46">46</a></span><span class="t"> <span class="key">for</span> <span class="nam">idx</span><span class="op">,</span> <span class="nam">title</span> <span class="key">in</span> <span class="nam">enumerate</span><span class="op">(</span><span class="nam">headlines</span><span class="op">,</span> <span class="nam">start</span><span class="op">=</span><span class="num">1</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t47" href="#t47">47</a></span><span class="t"> <span class="nam">prompt</span> <span class="op">+=</span> <span class="str">f"{idx}. {title}\n"</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t48" href="#t48">48</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t49" href="#t49">49</a></span><span class="t"> <span class="key">try</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t50" href="#t50">50</a></span><span class="t"> <span class="nam">result</span> <span class="op">=</span> <span class="nam">subprocess</span><span class="op">.</span><span class="nam">run</span><span class="op">(</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t51" href="#t51">51</a></span><span class="t"> <span class="op">[</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t52" href="#t52">52</a></span><span class="t"> <span class="str">'openclaw'</span><span class="op">,</span> <span class="str">'agent'</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t53" href="#t53">53</a></span><span class="t"> <span class="str">'--session-id'</span><span class="op">,</span> <span class="str">'finance-news-translate-portfolio'</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t54" href="#t54">54</a></span><span class="t"> <span class="str">'--message'</span><span class="op">,</span> <span class="nam">prompt</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t55" href="#t55">55</a></span><span class="t"> <span class="str">'--json'</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t56" href="#t56">56</a></span><span class="t"> <span class="str">'--timeout'</span><span class="op">,</span> <span class="str">'60'</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t57" href="#t57">57</a></span><span class="t"> <span class="op">]</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t58" href="#t58">58</a></span><span class="t"> <span class="nam">capture_output</span><span class="op">=</span><span class="key">True</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t59" href="#t59">59</a></span><span class="t"> <span class="nam">text</span><span class="op">=</span><span class="key">True</span><span class="op">,</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t60" href="#t60">60</a></span><span class="t"> <span class="nam">timeout</span><span class="op">=</span><span class="num">90</span> </span><span class="r"></span></p>
|
||||
<p class="mis mis2 show_mis"><span class="n"><a id="t61" href="#t61">61</a></span><span class="t"> <span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t62" href="#t62">62</a></span><span class="t"> <span class="key">except</span> <span class="op">(</span><span class="nam">subprocess</span><span class="op">.</span><span class="nam">TimeoutExpired</span><span class="op">,</span> <span class="nam">FileNotFoundError</span><span class="op">,</span> <span class="nam">OSError</span><span class="op">)</span> <span class="key">as</span> <span class="nam">e</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t63" href="#t63">63</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"⚠️ Translation failed: {e}"</span><span class="op">,</span> <span class="nam">file</span><span class="op">=</span><span class="nam">sys</span><span class="op">.</span><span class="nam">stderr</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t64" href="#t64">64</a></span><span class="t"> <span class="key">return</span> <span class="nam">headlines</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t65" href="#t65">65</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t66" href="#t66">66</a></span><span class="t"> <span class="key">if</span> <span class="nam">result</span><span class="op">.</span><span class="nam">returncode</span> <span class="op">!=</span> <span class="num">0</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t67" href="#t67">67</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"⚠️ openclaw error: {result.stderr}"</span><span class="op">,</span> <span class="nam">file</span><span class="op">=</span><span class="nam">sys</span><span class="op">.</span><span class="nam">stderr</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t68" href="#t68">68</a></span><span class="t"> <span class="key">return</span> <span class="nam">headlines</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t69" href="#t69">69</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t70" href="#t70">70</a></span><span class="t"> <span class="com"># Extract reply from openclaw JSON output</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t71" href="#t71">71</a></span><span class="t"> <span class="com"># Format: {"result": {"payloads": [{"text": "..."}]}}</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t72" href="#t72">72</a></span><span class="t"> <span class="com"># Note: openclaw may print plugin loading messages before JSON, so find the JSON start</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t73" href="#t73">73</a></span><span class="t"> <span class="nam">stdout</span> <span class="op">=</span> <span class="nam">result</span><span class="op">.</span><span class="nam">stdout</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t74" href="#t74">74</a></span><span class="t"> <span class="nam">json_start</span> <span class="op">=</span> <span class="nam">stdout</span><span class="op">.</span><span class="nam">find</span><span class="op">(</span><span class="str">'{'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t75" href="#t75">75</a></span><span class="t"> <span class="key">if</span> <span class="nam">json_start</span> <span class="op">></span> <span class="num">0</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t76" href="#t76">76</a></span><span class="t"> <span class="nam">stdout</span> <span class="op">=</span> <span class="nam">stdout</span><span class="op">[</span><span class="nam">json_start</span><span class="op">:</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t77" href="#t77">77</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t78" href="#t78">78</a></span><span class="t"> <span class="key">try</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t79" href="#t79">79</a></span><span class="t"> <span class="nam">output</span> <span class="op">=</span> <span class="nam">json</span><span class="op">.</span><span class="nam">loads</span><span class="op">(</span><span class="nam">stdout</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t80" href="#t80">80</a></span><span class="t"> <span class="nam">payloads</span> <span class="op">=</span> <span class="nam">output</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'result'</span><span class="op">,</span> <span class="op">{</span><span class="op">}</span><span class="op">)</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'payloads'</span><span class="op">,</span> <span class="op">[</span><span class="op">]</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t81" href="#t81">81</a></span><span class="t"> <span class="key">if</span> <span class="nam">payloads</span> <span class="key">and</span> <span class="nam">payloads</span><span class="op">[</span><span class="num">0</span><span class="op">]</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'text'</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t82" href="#t82">82</a></span><span class="t"> <span class="nam">reply</span> <span class="op">=</span> <span class="nam">payloads</span><span class="op">[</span><span class="num">0</span><span class="op">]</span><span class="op">[</span><span class="str">'text'</span><span class="op">]</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t83" href="#t83">83</a></span><span class="t"> <span class="key">else</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t84" href="#t84">84</a></span><span class="t"> <span class="nam">reply</span> <span class="op">=</span> <span class="nam">output</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'reply'</span><span class="op">,</span> <span class="str">''</span><span class="op">)</span> <span class="key">or</span> <span class="nam">output</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'message'</span><span class="op">,</span> <span class="str">''</span><span class="op">)</span> <span class="key">or</span> <span class="nam">stdout</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t85" href="#t85">85</a></span><span class="t"> <span class="key">except</span> <span class="nam">json</span><span class="op">.</span><span class="nam">JSONDecodeError</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t86" href="#t86">86</a></span><span class="t"> <span class="nam">reply</span> <span class="op">=</span> <span class="nam">stdout</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t87" href="#t87">87</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t88" href="#t88">88</a></span><span class="t"> <span class="com"># Parse JSON array from reply</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t89" href="#t89">89</a></span><span class="t"> <span class="nam">json_text</span> <span class="op">=</span> <span class="nam">reply</span><span class="op">.</span><span class="nam">strip</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t90" href="#t90">90</a></span><span class="t"> <span class="key">if</span> <span class="str">"```"</span> <span class="key">in</span> <span class="nam">json_text</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t91" href="#t91">91</a></span><span class="t"> <span class="nam">match</span> <span class="op">=</span> <span class="nam">re</span><span class="op">.</span><span class="nam">search</span><span class="op">(</span><span class="str">r'```(?:json)?\s*(.*?)```'</span><span class="op">,</span> <span class="nam">json_text</span><span class="op">,</span> <span class="nam">re</span><span class="op">.</span><span class="nam">DOTALL</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t92" href="#t92">92</a></span><span class="t"> <span class="key">if</span> <span class="nam">match</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t93" href="#t93">93</a></span><span class="t"> <span class="nam">json_text</span> <span class="op">=</span> <span class="nam">match</span><span class="op">.</span><span class="nam">group</span><span class="op">(</span><span class="num">1</span><span class="op">)</span><span class="op">.</span><span class="nam">strip</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t94" href="#t94">94</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t95" href="#t95">95</a></span><span class="t"> <span class="key">try</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t96" href="#t96">96</a></span><span class="t"> <span class="nam">translated</span> <span class="op">=</span> <span class="nam">json</span><span class="op">.</span><span class="nam">loads</span><span class="op">(</span><span class="nam">json_text</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t97" href="#t97">97</a></span><span class="t"> <span class="key">if</span> <span class="nam">isinstance</span><span class="op">(</span><span class="nam">translated</span><span class="op">,</span> <span class="nam">list</span><span class="op">)</span> <span class="key">and</span> <span class="nam">len</span><span class="op">(</span><span class="nam">translated</span><span class="op">)</span> <span class="op">==</span> <span class="nam">len</span><span class="op">(</span><span class="nam">headlines</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t98" href="#t98">98</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"✅ Translated {len(headlines)} portfolio headlines"</span><span class="op">,</span> <span class="nam">file</span><span class="op">=</span><span class="nam">sys</span><span class="op">.</span><span class="nam">stderr</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t99" href="#t99">99</a></span><span class="t"> <span class="key">return</span> <span class="nam">translated</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t100" href="#t100">100</a></span><span class="t"> <span class="key">except</span> <span class="nam">json</span><span class="op">.</span><span class="nam">JSONDecodeError</span> <span class="key">as</span> <span class="nam">e</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t101" href="#t101">101</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"⚠️ JSON parse error: {e}"</span><span class="op">,</span> <span class="nam">file</span><span class="op">=</span><span class="nam">sys</span><span class="op">.</span><span class="nam">stderr</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t102" href="#t102">102</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t103" href="#t103">103</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"⚠️ Translation failed, using original headlines"</span><span class="op">,</span> <span class="nam">file</span><span class="op">=</span><span class="nam">sys</span><span class="op">.</span><span class="nam">stderr</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t104" href="#t104">104</a></span><span class="t"> <span class="key">return</span> <span class="nam">headlines</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t105" href="#t105">105</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t106" href="#t106">106</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t107" href="#t107">107</a></span><span class="t"><span class="key">def</span> <span class="nam">replace_headlines</span><span class="op">(</span><span class="nam">portfolio_message</span><span class="op">:</span> <span class="nam">str</span><span class="op">,</span> <span class="nam">original</span><span class="op">:</span> <span class="nam">list</span><span class="op">[</span><span class="nam">str</span><span class="op">]</span><span class="op">,</span> <span class="nam">translated</span><span class="op">:</span> <span class="nam">list</span><span class="op">[</span><span class="nam">str</span><span class="op">]</span><span class="op">)</span> <span class="op">-></span> <span class="nam">str</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t108" href="#t108">108</a></span><span class="t"> <span class="str">"""Replace original headlines with translated ones in portfolio message."""</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t109" href="#t109">109</a></span><span class="t"> <span class="nam">result</span> <span class="op">=</span> <span class="nam">portfolio_message</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t110" href="#t110">110</a></span><span class="t"> <span class="key">for</span> <span class="nam">orig</span><span class="op">,</span> <span class="nam">trans</span> <span class="key">in</span> <span class="nam">zip</span><span class="op">(</span><span class="nam">original</span><span class="op">,</span> <span class="nam">translated</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t111" href="#t111">111</a></span><span class="t"> <span class="key">if</span> <span class="nam">orig</span> <span class="op">!=</span> <span class="nam">trans</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t112" href="#t112">112</a></span><span class="t"> <span class="com"># Replace the headline text, preserving bullet and reference</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t113" href="#t113">113</a></span><span class="t"> <span class="nam">result</span> <span class="op">=</span> <span class="nam">result</span><span class="op">.</span><span class="nam">replace</span><span class="op">(</span><span class="str">f"• {orig}"</span><span class="op">,</span> <span class="str">f"• {trans}"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t114" href="#t114">114</a></span><span class="t"> <span class="key">return</span> <span class="nam">result</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t115" href="#t115">115</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t116" href="#t116">116</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t117" href="#t117">117</a></span><span class="t"><span class="key">def</span> <span class="nam">main</span><span class="op">(</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t118" href="#t118">118</a></span><span class="t"> <span class="nam">parser</span> <span class="op">=</span> <span class="nam">argparse</span><span class="op">.</span><span class="nam">ArgumentParser</span><span class="op">(</span><span class="nam">description</span><span class="op">=</span><span class="str">'Translate portfolio headlines'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t119" href="#t119">119</a></span><span class="t"> <span class="nam">parser</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">'json_file'</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">'Path to briefing JSON file'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t120" href="#t120">120</a></span><span class="t"> <span class="nam">parser</span><span class="op">.</span><span class="nam">add_argument</span><span class="op">(</span><span class="str">'--lang'</span><span class="op">,</span> <span class="nam">default</span><span class="op">=</span><span class="str">'de'</span><span class="op">,</span> <span class="nam">help</span><span class="op">=</span><span class="str">'Target language (default: de)'</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t121" href="#t121">121</a></span><span class="t"> <span class="nam">args</span> <span class="op">=</span> <span class="nam">parser</span><span class="op">.</span><span class="nam">parse_args</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t122" href="#t122">122</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t123" href="#t123">123</a></span><span class="t"> <span class="com"># Read JSON</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t124" href="#t124">124</a></span><span class="t"> <span class="key">try</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t125" href="#t125">125</a></span><span class="t"> <span class="key">with</span> <span class="nam">open</span><span class="op">(</span><span class="nam">args</span><span class="op">.</span><span class="nam">json_file</span><span class="op">,</span> <span class="str">'r'</span><span class="op">)</span> <span class="key">as</span> <span class="nam">f</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t126" href="#t126">126</a></span><span class="t"> <span class="nam">data</span> <span class="op">=</span> <span class="nam">json</span><span class="op">.</span><span class="nam">load</span><span class="op">(</span><span class="nam">f</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t127" href="#t127">127</a></span><span class="t"> <span class="key">except</span> <span class="op">(</span><span class="nam">FileNotFoundError</span><span class="op">,</span> <span class="nam">json</span><span class="op">.</span><span class="nam">JSONDecodeError</span><span class="op">)</span> <span class="key">as</span> <span class="nam">e</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t128" href="#t128">128</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"❌ Error reading {args.json_file}: {e}"</span><span class="op">,</span> <span class="nam">file</span><span class="op">=</span><span class="nam">sys</span><span class="op">.</span><span class="nam">stderr</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t129" href="#t129">129</a></span><span class="t"> <span class="nam">sys</span><span class="op">.</span><span class="nam">exit</span><span class="op">(</span><span class="num">1</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t130" href="#t130">130</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t131" href="#t131">131</a></span><span class="t"> <span class="nam">portfolio_message</span> <span class="op">=</span> <span class="nam">data</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">'portfolio_message'</span><span class="op">,</span> <span class="str">''</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t132" href="#t132">132</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">portfolio_message</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t133" href="#t133">133</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">"No portfolio_message to translate"</span><span class="op">,</span> <span class="nam">file</span><span class="op">=</span><span class="nam">sys</span><span class="op">.</span><span class="nam">stderr</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t134" href="#t134">134</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="nam">json</span><span class="op">.</span><span class="nam">dumps</span><span class="op">(</span><span class="nam">data</span><span class="op">,</span> <span class="nam">ensure_ascii</span><span class="op">=</span><span class="key">False</span><span class="op">,</span> <span class="nam">indent</span><span class="op">=</span><span class="num">2</span><span class="op">)</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t135" href="#t135">135</a></span><span class="t"> <span class="key">return</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t136" href="#t136">136</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t137" href="#t137">137</a></span><span class="t"> <span class="com"># Extract, translate, replace</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t138" href="#t138">138</a></span><span class="t"> <span class="nam">headlines</span> <span class="op">=</span> <span class="nam">extract_headlines</span><span class="op">(</span><span class="nam">portfolio_message</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t139" href="#t139">139</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">headlines</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t140" href="#t140">140</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">"No headlines found in portfolio_message"</span><span class="op">,</span> <span class="nam">file</span><span class="op">=</span><span class="nam">sys</span><span class="op">.</span><span class="nam">stderr</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t141" href="#t141">141</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="nam">json</span><span class="op">.</span><span class="nam">dumps</span><span class="op">(</span><span class="nam">data</span><span class="op">,</span> <span class="nam">ensure_ascii</span><span class="op">=</span><span class="key">False</span><span class="op">,</span> <span class="nam">indent</span><span class="op">=</span><span class="num">2</span><span class="op">)</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t142" href="#t142">142</a></span><span class="t"> <span class="key">return</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t143" href="#t143">143</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t144" href="#t144">144</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"📝 Found {len(headlines)} headlines to translate"</span><span class="op">,</span> <span class="nam">file</span><span class="op">=</span><span class="nam">sys</span><span class="op">.</span><span class="nam">stderr</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t145" href="#t145">145</a></span><span class="t"> <span class="nam">translated</span> <span class="op">=</span> <span class="nam">translate_headlines</span><span class="op">(</span><span class="nam">headlines</span><span class="op">,</span> <span class="nam">args</span><span class="op">.</span><span class="nam">lang</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t146" href="#t146">146</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t147" href="#t147">147</a></span><span class="t"> <span class="com"># Update portfolio message</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t148" href="#t148">148</a></span><span class="t"> <span class="nam">data</span><span class="op">[</span><span class="str">'portfolio_message'</span><span class="op">]</span> <span class="op">=</span> <span class="nam">replace_headlines</span><span class="op">(</span><span class="nam">portfolio_message</span><span class="op">,</span> <span class="nam">headlines</span><span class="op">,</span> <span class="nam">translated</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t149" href="#t149">149</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t150" href="#t150">150</a></span><span class="t"> <span class="com"># Write back</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t151" href="#t151">151</a></span><span class="t"> <span class="key">with</span> <span class="nam">open</span><span class="op">(</span><span class="nam">args</span><span class="op">.</span><span class="nam">json_file</span><span class="op">,</span> <span class="str">'w'</span><span class="op">)</span> <span class="key">as</span> <span class="nam">f</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t152" href="#t152">152</a></span><span class="t"> <span class="nam">json</span><span class="op">.</span><span class="nam">dump</span><span class="op">(</span><span class="nam">data</span><span class="op">,</span> <span class="nam">f</span><span class="op">,</span> <span class="nam">ensure_ascii</span><span class="op">=</span><span class="key">False</span><span class="op">,</span> <span class="nam">indent</span><span class="op">=</span><span class="num">2</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t153" href="#t153">153</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t154" href="#t154">154</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">f"✅ Updated {args.json_file}"</span><span class="op">,</span> <span class="nam">file</span><span class="op">=</span><span class="nam">sys</span><span class="op">.</span><span class="nam">stderr</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t155" href="#t155">155</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t156" href="#t156">156</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="exc show_exc"><span class="n"><a id="t157" href="#t157">157</a></span><span class="t"><span class="key">if</span> <span class="nam">__name__</span> <span class="op">==</span> <span class="str">'__main__'</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="exc show_exc"><span class="n"><a id="t158" href="#t158">158</a></span><span class="t"> <span class="nam">main</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
</main>
|
||||
<footer>
|
||||
<div class="content">
|
||||
<p>
|
||||
<a class="nav" href="z_de1a740d5dc98ffd_summarize_py.html">« prev</a>
|
||||
<a class="nav" href="index.html">^ index</a>
|
||||
<a class="nav" href="z_de1a740d5dc98ffd_utils_py.html">» next</a>
|
||||
|
||||
<a class="nav" href="https://coverage.readthedocs.io/en/7.13.2">coverage.py v7.13.2</a>,
|
||||
created at 2026-02-01 16:34 -0800
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
142
htmlcov/z_de1a740d5dc98ffd_utils_py.html
generated
Normal file
142
htmlcov/z_de1a740d5dc98ffd_utils_py.html
generated
Normal file
@@ -0,0 +1,142 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>Coverage for scripts/utils.py: 71%</title>
|
||||
<link rel="icon" sizes="32x32" href="favicon_32_cb_c827f16f.png">
|
||||
<link rel="stylesheet" href="style_cb_9ff733b0.css" type="text/css">
|
||||
<script src="coverage_html_cb_dd2e7eb5.js" defer></script>
|
||||
</head>
|
||||
<body class="pyfile">
|
||||
<header>
|
||||
<div class="content">
|
||||
<h1>
|
||||
<span class="text">Coverage for </span><b>scripts / utils.py</b>:
|
||||
<span class="pc_cov">71%</span>
|
||||
</h1>
|
||||
<aside id="help_panel_wrapper">
|
||||
<input id="help_panel_state" type="checkbox">
|
||||
<label for="help_panel_state">
|
||||
<img id="keyboard_icon" src="keybd_closed_cb_900cfef5.png" alt="Show/hide keyboard shortcuts">
|
||||
</label>
|
||||
<div id="help_panel">
|
||||
<p class="legend">Shortcuts on this page</p>
|
||||
<div class="keyhelp">
|
||||
<p>
|
||||
<kbd>r</kbd>
|
||||
<kbd>m</kbd>
|
||||
<kbd>x</kbd>
|
||||
toggle line displays
|
||||
</p>
|
||||
<p>
|
||||
<kbd>j</kbd>
|
||||
<kbd>k</kbd>
|
||||
next/prev highlighted chunk
|
||||
</p>
|
||||
<p>
|
||||
<kbd>0</kbd> (zero) top of page
|
||||
</p>
|
||||
<p>
|
||||
<kbd>1</kbd> (one) first highlighted chunk
|
||||
</p>
|
||||
<p>
|
||||
<kbd>[</kbd>
|
||||
<kbd>]</kbd>
|
||||
prev/next file
|
||||
</p>
|
||||
<p>
|
||||
<kbd>u</kbd> up to the index
|
||||
</p>
|
||||
<p>
|
||||
<kbd>?</kbd> show/hide this help
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
<h2>
|
||||
<span class="text">34 statements </span>
|
||||
<button type="button" class="run button_toggle_run" value="run" data-shortcut="r" title="Toggle lines run">24<span class="text"> run</span></button>
|
||||
<button type="button" class="mis show_mis button_toggle_mis" value="mis" data-shortcut="m" title="Toggle lines missing">10<span class="text"> missing</span></button>
|
||||
<button type="button" class="exc show_exc button_toggle_exc" value="exc" data-shortcut="x" title="Toggle lines excluded">0<span class="text"> excluded</span></button>
|
||||
</h2>
|
||||
<p class="text">
|
||||
<a id="prevFileLink" class="nav" href="z_de1a740d5dc98ffd_translate_portfolio_py.html">« prev</a>
|
||||
<a id="indexLink" class="nav" href="index.html">^ index</a>
|
||||
<a id="nextFileLink" class="nav" href="index.html">» next</a>
|
||||
|
||||
<a class="nav" href="https://coverage.readthedocs.io/en/7.13.2">coverage.py v7.13.2</a>,
|
||||
created at 2026-02-01 16:34 -0800
|
||||
</p>
|
||||
<aside class="hidden">
|
||||
<button type="button" class="button_next_chunk" data-shortcut="j"></button>
|
||||
<button type="button" class="button_prev_chunk" data-shortcut="k"></button>
|
||||
<button type="button" class="button_top_of_page" data-shortcut="0"></button>
|
||||
<button type="button" class="button_first_chunk" data-shortcut="1"></button>
|
||||
<button type="button" class="button_prev_file" data-shortcut="["></button>
|
||||
<button type="button" class="button_next_file" data-shortcut="]"></button>
|
||||
<button type="button" class="button_to_index" data-shortcut="u"></button>
|
||||
<button type="button" class="button_show_hide_help" data-shortcut="?"></button>
|
||||
</aside>
|
||||
</div>
|
||||
</header>
|
||||
<main id="source">
|
||||
<p class="pln"><span class="n"><a id="t1" href="#t1">1</a></span><span class="t"><span class="str">"""Shared helpers."""</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t2" href="#t2">2</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t3" href="#t3">3</a></span><span class="t"><span class="key">import</span> <span class="nam">os</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t4" href="#t4">4</a></span><span class="t"><span class="key">import</span> <span class="nam">sys</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t5" href="#t5">5</a></span><span class="t"><span class="key">import</span> <span class="nam">time</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t6" href="#t6">6</a></span><span class="t"><span class="key">from</span> <span class="nam">pathlib</span> <span class="key">import</span> <span class="nam">Path</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t7" href="#t7">7</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t8" href="#t8">8</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t9" href="#t9">9</a></span><span class="t"><span class="key">def</span> <span class="nam">ensure_venv</span><span class="op">(</span><span class="op">)</span> <span class="op">-></span> <span class="key">None</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t10" href="#t10">10</a></span><span class="t"> <span class="str">"""Re-exec inside local venv if available and not already active."""</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t11" href="#t11">11</a></span><span class="t"> <span class="key">if</span> <span class="nam">os</span><span class="op">.</span><span class="nam">environ</span><span class="op">.</span><span class="nam">get</span><span class="op">(</span><span class="str">"FINANCE_NEWS_VENV_BOOTSTRAPPED"</span><span class="op">)</span> <span class="op">==</span> <span class="str">"1"</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t12" href="#t12">12</a></span><span class="t"> <span class="key">return</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t13" href="#t13">13</a></span><span class="t"> <span class="key">if</span> <span class="nam">sys</span><span class="op">.</span><span class="nam">prefix</span> <span class="op">!=</span> <span class="nam">sys</span><span class="op">.</span><span class="nam">base_prefix</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t14" href="#t14">14</a></span><span class="t"> <span class="key">return</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t15" href="#t15">15</a></span><span class="t"> <span class="nam">venv_python</span> <span class="op">=</span> <span class="nam">Path</span><span class="op">(</span><span class="nam">__file__</span><span class="op">)</span><span class="op">.</span><span class="nam">resolve</span><span class="op">(</span><span class="op">)</span><span class="op">.</span><span class="nam">parent</span><span class="op">.</span><span class="nam">parent</span> <span class="op">/</span> <span class="str">"venv"</span> <span class="op">/</span> <span class="str">"bin"</span> <span class="op">/</span> <span class="str">"python3"</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t16" href="#t16">16</a></span><span class="t"> <span class="key">if</span> <span class="key">not</span> <span class="nam">venv_python</span><span class="op">.</span><span class="nam">exists</span><span class="op">(</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t17" href="#t17">17</a></span><span class="t"> <span class="nam">print</span><span class="op">(</span><span class="str">"⚠️ finance-news venv missing; run scripts from the repo venv to avoid dependency errors."</span><span class="op">,</span> <span class="nam">file</span><span class="op">=</span><span class="nam">sys</span><span class="op">.</span><span class="nam">stderr</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t18" href="#t18">18</a></span><span class="t"> <span class="key">return</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t19" href="#t19">19</a></span><span class="t"> <span class="nam">env</span> <span class="op">=</span> <span class="nam">os</span><span class="op">.</span><span class="nam">environ</span><span class="op">.</span><span class="nam">copy</span><span class="op">(</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t20" href="#t20">20</a></span><span class="t"> <span class="nam">env</span><span class="op">[</span><span class="str">"FINANCE_NEWS_VENV_BOOTSTRAPPED"</span><span class="op">]</span> <span class="op">=</span> <span class="str">"1"</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t21" href="#t21">21</a></span><span class="t"> <span class="nam">os</span><span class="op">.</span><span class="nam">execvpe</span><span class="op">(</span><span class="nam">str</span><span class="op">(</span><span class="nam">venv_python</span><span class="op">)</span><span class="op">,</span> <span class="op">[</span><span class="nam">str</span><span class="op">(</span><span class="nam">venv_python</span><span class="op">)</span><span class="op">]</span> <span class="op">+</span> <span class="nam">sys</span><span class="op">.</span><span class="nam">argv</span><span class="op">,</span> <span class="nam">env</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t22" href="#t22">22</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t23" href="#t23">23</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t24" href="#t24">24</a></span><span class="t"><span class="key">def</span> <span class="nam">compute_deadline</span><span class="op">(</span><span class="nam">deadline_sec</span><span class="op">:</span> <span class="nam">int</span> <span class="op">|</span> <span class="key">None</span><span class="op">)</span> <span class="op">-></span> <span class="nam">float</span> <span class="op">|</span> <span class="key">None</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t25" href="#t25">25</a></span><span class="t"> <span class="key">if</span> <span class="nam">deadline_sec</span> <span class="key">is</span> <span class="key">None</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t26" href="#t26">26</a></span><span class="t"> <span class="key">return</span> <span class="key">None</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t27" href="#t27">27</a></span><span class="t"> <span class="key">if</span> <span class="nam">deadline_sec</span> <span class="op"><=</span> <span class="num">0</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="mis show_mis"><span class="n"><a id="t28" href="#t28">28</a></span><span class="t"> <span class="key">return</span> <span class="key">None</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t29" href="#t29">29</a></span><span class="t"> <span class="key">return</span> <span class="nam">time</span><span class="op">.</span><span class="nam">monotonic</span><span class="op">(</span><span class="op">)</span> <span class="op">+</span> <span class="nam">deadline_sec</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t30" href="#t30">30</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t31" href="#t31">31</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t32" href="#t32">32</a></span><span class="t"><span class="key">def</span> <span class="nam">time_left</span><span class="op">(</span><span class="nam">deadline</span><span class="op">:</span> <span class="nam">float</span> <span class="op">|</span> <span class="key">None</span><span class="op">)</span> <span class="op">-></span> <span class="nam">int</span> <span class="op">|</span> <span class="key">None</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t33" href="#t33">33</a></span><span class="t"> <span class="key">if</span> <span class="nam">deadline</span> <span class="key">is</span> <span class="key">None</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t34" href="#t34">34</a></span><span class="t"> <span class="key">return</span> <span class="key">None</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t35" href="#t35">35</a></span><span class="t"> <span class="nam">remaining</span> <span class="op">=</span> <span class="nam">int</span><span class="op">(</span><span class="nam">deadline</span> <span class="op">-</span> <span class="nam">time</span><span class="op">.</span><span class="nam">monotonic</span><span class="op">(</span><span class="op">)</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t36" href="#t36">36</a></span><span class="t"> <span class="key">return</span> <span class="nam">remaining</span> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t37" href="#t37">37</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="pln"><span class="n"><a id="t38" href="#t38">38</a></span><span class="t"> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t39" href="#t39">39</a></span><span class="t"><span class="key">def</span> <span class="nam">clamp_timeout</span><span class="op">(</span><span class="nam">default_timeout</span><span class="op">:</span> <span class="nam">int</span><span class="op">,</span> <span class="nam">deadline</span><span class="op">:</span> <span class="nam">float</span> <span class="op">|</span> <span class="key">None</span><span class="op">,</span> <span class="nam">minimum</span><span class="op">:</span> <span class="nam">int</span> <span class="op">=</span> <span class="num">1</span><span class="op">)</span> <span class="op">-></span> <span class="nam">int</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t40" href="#t40">40</a></span><span class="t"> <span class="nam">remaining</span> <span class="op">=</span> <span class="nam">time_left</span><span class="op">(</span><span class="nam">deadline</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t41" href="#t41">41</a></span><span class="t"> <span class="key">if</span> <span class="nam">remaining</span> <span class="key">is</span> <span class="key">None</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t42" href="#t42">42</a></span><span class="t"> <span class="key">return</span> <span class="nam">default_timeout</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t43" href="#t43">43</a></span><span class="t"> <span class="key">if</span> <span class="nam">remaining</span> <span class="op"><=</span> <span class="num">0</span><span class="op">:</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t44" href="#t44">44</a></span><span class="t"> <span class="key">raise</span> <span class="nam">TimeoutError</span><span class="op">(</span><span class="str">"Deadline exceeded"</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
<p class="run"><span class="n"><a id="t45" href="#t45">45</a></span><span class="t"> <span class="key">return</span> <span class="nam">max</span><span class="op">(</span><span class="nam">min</span><span class="op">(</span><span class="nam">default_timeout</span><span class="op">,</span> <span class="nam">remaining</span><span class="op">)</span><span class="op">,</span> <span class="nam">minimum</span><span class="op">)</span> </span><span class="r"></span></p>
|
||||
</main>
|
||||
<footer>
|
||||
<div class="content">
|
||||
<p>
|
||||
<a class="nav" href="z_de1a740d5dc98ffd_translate_portfolio_py.html">« prev</a>
|
||||
<a class="nav" href="index.html">^ index</a>
|
||||
<a class="nav" href="index.html">» next</a>
|
||||
|
||||
<a class="nav" href="https://coverage.readthedocs.io/en/7.13.2">coverage.py v7.13.2</a>,
|
||||
created at 2026-02-01 16:34 -0800
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
53
pyproject.toml
Normal file
53
pyproject.toml
Normal file
@@ -0,0 +1,53 @@
|
||||
[project]
|
||||
name = "finance-news"
|
||||
version = "1.0.0"
|
||||
description = "Finance news aggregation and market briefing skill for OpenClaw"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
license = { text = "MIT" }
|
||||
authors = [{ name = "Martin Kessler", email = "martin@kessler.io" }]
|
||||
|
||||
dependencies = [
|
||||
"feedparser>=6.0.11",
|
||||
"yfinance>=0.2.40",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
"pytest>=8.0",
|
||||
"pytest-cov>=5.0",
|
||||
"ruff>=0.4",
|
||||
]
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["scripts"]
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
testpaths = ["tests"]
|
||||
python_files = ["test_*.py"]
|
||||
python_functions = ["test_*"]
|
||||
addopts = "-v --tb=short"
|
||||
|
||||
[tool.coverage.run]
|
||||
source = ["scripts"]
|
||||
omit = ["scripts/__pycache__/*"]
|
||||
|
||||
[tool.coverage.report]
|
||||
exclude_lines = [
|
||||
"pragma: no cover",
|
||||
"if __name__ == .__main__.:",
|
||||
"raise NotImplementedError",
|
||||
]
|
||||
fail_under = 30 # Current coverage is ~39%
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 120
|
||||
target-version = "py310"
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = ["E", "F", "W", "I", "UP"]
|
||||
ignore = ["E501"]
|
||||
12
pytest.ini
Normal file
12
pytest.ini
Normal file
@@ -0,0 +1,12 @@
|
||||
[pytest]
|
||||
testpaths = tests
|
||||
python_files = test_*.py
|
||||
python_classes = Test*
|
||||
python_functions = test_*
|
||||
addopts =
|
||||
-v
|
||||
--strict-markers
|
||||
--tb=short
|
||||
--cov=scripts
|
||||
--cov-report=term-missing
|
||||
--cov-report=html
|
||||
4
requirements-test.txt
Normal file
4
requirements-test.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
# Test dependencies
|
||||
pytest>=7.4.0
|
||||
pytest-cov>=4.1.0
|
||||
pytest-mock>=3.12.0
|
||||
2
requirements.txt
Normal file
2
requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
feedparser>=6.0.11
|
||||
yfinance
|
||||
500
scripts/alerts.py
Normal file
500
scripts/alerts.py
Normal file
@@ -0,0 +1,500 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Price Target Alerts - Track buy zone alerts for stocks.
|
||||
|
||||
Features:
|
||||
- Set price target alerts (buy zone triggers)
|
||||
- Check alerts against current prices
|
||||
- Snooze, update, delete alerts
|
||||
- Multi-currency support (USD, EUR, JPY, SGD, MXN)
|
||||
|
||||
Usage:
|
||||
alerts.py list # Show all alerts
|
||||
alerts.py set CRWD 400 --note 'Kaufzone' # Set alert
|
||||
alerts.py check # Check triggered alerts
|
||||
alerts.py delete CRWD # Delete alert
|
||||
alerts.py snooze CRWD --days 7 # Snooze for 7 days
|
||||
alerts.py update CRWD 380 # Update target price
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
|
||||
from utils import ensure_venv
|
||||
|
||||
ensure_venv()
|
||||
|
||||
# Lazy import to avoid numpy issues at module load
|
||||
fetch_market_data = None
|
||||
|
||||
def get_fetch_market_data():
|
||||
global fetch_market_data
|
||||
if fetch_market_data is None:
|
||||
from fetch_news import fetch_market_data as fmd
|
||||
fetch_market_data = fmd
|
||||
return fetch_market_data
|
||||
|
||||
SCRIPT_DIR = Path(__file__).parent
|
||||
CONFIG_DIR = SCRIPT_DIR.parent / "config"
|
||||
ALERTS_FILE = CONFIG_DIR / "alerts.json"
|
||||
|
||||
SUPPORTED_CURRENCIES = ["USD", "EUR", "JPY", "SGD", "MXN"]
|
||||
|
||||
|
||||
def load_alerts() -> dict:
|
||||
"""Load alerts from JSON file."""
|
||||
if not ALERTS_FILE.exists():
|
||||
return {"_meta": {"version": 1, "supported_currencies": SUPPORTED_CURRENCIES}, "alerts": []}
|
||||
return json.loads(ALERTS_FILE.read_text())
|
||||
|
||||
|
||||
def save_alerts(data: dict) -> None:
|
||||
"""Save alerts to JSON file."""
|
||||
data["_meta"]["updated_at"] = datetime.now().isoformat()
|
||||
ALERTS_FILE.write_text(json.dumps(data, indent=2))
|
||||
|
||||
|
||||
def get_alert_by_ticker(alerts: list, ticker: str) -> dict | None:
|
||||
"""Find alert by ticker."""
|
||||
ticker = ticker.upper()
|
||||
for alert in alerts:
|
||||
if alert["ticker"] == ticker:
|
||||
return alert
|
||||
return None
|
||||
|
||||
|
||||
def format_price(price: float, currency: str) -> str:
|
||||
"""Format price with currency symbol."""
|
||||
symbols = {"USD": "$", "EUR": "€", "JPY": "¥", "SGD": "S$", "MXN": "MX$"}
|
||||
symbol = symbols.get(currency, currency + " ")
|
||||
if currency == "JPY":
|
||||
return f"{symbol}{price:,.0f}"
|
||||
return f"{symbol}{price:,.2f}"
|
||||
|
||||
|
||||
def cmd_list(args) -> None:
|
||||
"""List all alerts."""
|
||||
data = load_alerts()
|
||||
alerts = data.get("alerts", [])
|
||||
|
||||
if not alerts:
|
||||
print("📭 No price alerts set")
|
||||
return
|
||||
|
||||
print(f"📊 Price Alerts ({len(alerts)} total)\n")
|
||||
|
||||
now = datetime.now()
|
||||
active = []
|
||||
snoozed = []
|
||||
|
||||
for alert in alerts:
|
||||
snooze_until = alert.get("snooze_until")
|
||||
if snooze_until and datetime.fromisoformat(snooze_until) > now:
|
||||
snoozed.append(alert)
|
||||
else:
|
||||
active.append(alert)
|
||||
|
||||
if active:
|
||||
print("### Active Alerts")
|
||||
for a in active:
|
||||
target = format_price(a["target_price"], a.get("currency", "USD"))
|
||||
note = f' — "{a["note"]}"' if a.get("note") else ""
|
||||
user = f" (by {a['set_by']})" if a.get("set_by") else ""
|
||||
print(f" • {a['ticker']}: {target}{note}{user}")
|
||||
print()
|
||||
|
||||
if snoozed:
|
||||
print("### Snoozed")
|
||||
for a in snoozed:
|
||||
target = format_price(a["target_price"], a.get("currency", "USD"))
|
||||
until = datetime.fromisoformat(a["snooze_until"]).strftime("%Y-%m-%d")
|
||||
print(f" • {a['ticker']}: {target} (until {until})")
|
||||
print()
|
||||
|
||||
|
||||
def cmd_set(args) -> None:
|
||||
"""Set a new alert."""
|
||||
data = load_alerts()
|
||||
alerts = data.get("alerts", [])
|
||||
ticker = args.ticker.upper()
|
||||
|
||||
# Check if alert exists
|
||||
existing = get_alert_by_ticker(alerts, ticker)
|
||||
if existing:
|
||||
print(f"⚠️ Alert for {ticker} already exists. Use 'update' to change target.")
|
||||
return
|
||||
|
||||
# Validate target price
|
||||
if args.target <= 0:
|
||||
print(f"❌ Target price must be greater than 0")
|
||||
return
|
||||
|
||||
currency = args.currency.upper() if args.currency else "USD"
|
||||
if currency not in SUPPORTED_CURRENCIES:
|
||||
print(f"❌ Currency {currency} not supported. Use: {', '.join(SUPPORTED_CURRENCIES)}")
|
||||
return
|
||||
|
||||
# Warn about currency mismatch based on ticker suffix
|
||||
ticker_currency_map = {
|
||||
".T": "JPY", # Tokyo
|
||||
".SI": "SGD", # Singapore
|
||||
".MX": "MXN", # Mexico
|
||||
".DE": "EUR", ".F": "EUR", ".PA": "EUR", # Europe
|
||||
}
|
||||
expected_currency = "USD" # Default for US stocks
|
||||
for suffix, curr in ticker_currency_map.items():
|
||||
if ticker.endswith(suffix):
|
||||
expected_currency = curr
|
||||
break
|
||||
|
||||
if currency != expected_currency:
|
||||
print(f"⚠️ Warning: {ticker} trades in {expected_currency}, but alert set in {currency}")
|
||||
|
||||
# Fetch current price (optional - may fail if numpy broken)
|
||||
current_price = None
|
||||
try:
|
||||
quotes = get_fetch_market_data()([ticker], timeout=10)
|
||||
if ticker in quotes and quotes[ticker].get("price"):
|
||||
current_price = quotes[ticker]["price"]
|
||||
except Exception as e:
|
||||
print(f"⚠️ Could not fetch current price: {e}", file=sys.stderr)
|
||||
|
||||
alert = {
|
||||
"ticker": ticker,
|
||||
"target_price": args.target,
|
||||
"currency": currency,
|
||||
"note": args.note or "",
|
||||
"set_by": args.user or "",
|
||||
"set_date": datetime.now().strftime("%Y-%m-%d"),
|
||||
"status": "active",
|
||||
"snooze_until": None,
|
||||
"triggered_count": 0,
|
||||
"last_triggered": None,
|
||||
}
|
||||
|
||||
alerts.append(alert)
|
||||
data["alerts"] = alerts
|
||||
save_alerts(data)
|
||||
|
||||
target_str = format_price(args.target, currency)
|
||||
print(f"✅ Alert set: {ticker} under {target_str}")
|
||||
if current_price:
|
||||
pct_diff = ((current_price - args.target) / current_price) * 100
|
||||
current_str = format_price(current_price, currency)
|
||||
print(f" Current: {current_str} ({pct_diff:+.1f}% to target)")
|
||||
|
||||
|
||||
def cmd_delete(args) -> None:
|
||||
"""Delete an alert."""
|
||||
data = load_alerts()
|
||||
alerts = data.get("alerts", [])
|
||||
ticker = args.ticker.upper()
|
||||
|
||||
new_alerts = [a for a in alerts if a["ticker"] != ticker]
|
||||
if len(new_alerts) == len(alerts):
|
||||
print(f"❌ No alert found for {ticker}")
|
||||
return
|
||||
|
||||
data["alerts"] = new_alerts
|
||||
save_alerts(data)
|
||||
print(f"🗑️ Alert deleted: {ticker}")
|
||||
|
||||
|
||||
def cmd_snooze(args) -> None:
|
||||
"""Snooze an alert."""
|
||||
data = load_alerts()
|
||||
alerts = data.get("alerts", [])
|
||||
ticker = args.ticker.upper()
|
||||
|
||||
alert = get_alert_by_ticker(alerts, ticker)
|
||||
if not alert:
|
||||
print(f"❌ No alert found for {ticker}")
|
||||
return
|
||||
|
||||
days = args.days or 7
|
||||
snooze_until = datetime.now() + timedelta(days=days)
|
||||
alert["snooze_until"] = snooze_until.isoformat()
|
||||
save_alerts(data)
|
||||
print(f"😴 Alert snoozed: {ticker} until {snooze_until.strftime('%Y-%m-%d')}")
|
||||
|
||||
|
||||
def cmd_update(args) -> None:
|
||||
"""Update alert target price."""
|
||||
data = load_alerts()
|
||||
alerts = data.get("alerts", [])
|
||||
ticker = args.ticker.upper()
|
||||
|
||||
alert = get_alert_by_ticker(alerts, ticker)
|
||||
if not alert:
|
||||
print(f"❌ No alert found for {ticker}")
|
||||
return
|
||||
|
||||
# Validate target price
|
||||
if args.target <= 0:
|
||||
print(f"❌ Target price must be greater than 0")
|
||||
return
|
||||
|
||||
old_target = alert["target_price"]
|
||||
alert["target_price"] = args.target
|
||||
if args.note:
|
||||
alert["note"] = args.note
|
||||
save_alerts(data)
|
||||
|
||||
currency = alert.get("currency", "USD")
|
||||
old_str = format_price(old_target, currency)
|
||||
new_str = format_price(args.target, currency)
|
||||
print(f"✏️ Alert updated: {ticker} {old_str} → {new_str}")
|
||||
|
||||
|
||||
def cmd_check(args) -> None:
|
||||
"""Check alerts against current prices."""
|
||||
data = load_alerts()
|
||||
alerts = data.get("alerts", [])
|
||||
|
||||
if not alerts:
|
||||
if args.json:
|
||||
print(json.dumps({"triggered": [], "watching": []}))
|
||||
else:
|
||||
print("📭 No alerts to check")
|
||||
return
|
||||
|
||||
now = datetime.now()
|
||||
active_alerts = []
|
||||
for alert in alerts:
|
||||
snooze_until = alert.get("snooze_until")
|
||||
if snooze_until and datetime.fromisoformat(snooze_until) > now:
|
||||
continue
|
||||
active_alerts.append(alert)
|
||||
|
||||
if not active_alerts:
|
||||
if args.json:
|
||||
print(json.dumps({"triggered": [], "watching": []}))
|
||||
else:
|
||||
print("📭 All alerts snoozed")
|
||||
return
|
||||
|
||||
# Fetch prices for all active alerts
|
||||
tickers = [a["ticker"] for a in active_alerts]
|
||||
quotes = get_fetch_market_data()(tickers, timeout=30)
|
||||
|
||||
triggered = []
|
||||
watching = []
|
||||
|
||||
for alert in active_alerts:
|
||||
ticker = alert["ticker"]
|
||||
target = alert["target_price"]
|
||||
currency = alert.get("currency", "USD")
|
||||
|
||||
quote = quotes.get(ticker, {})
|
||||
price = quote.get("price")
|
||||
|
||||
if price is None:
|
||||
continue
|
||||
|
||||
# Divide-by-zero protection
|
||||
if target == 0:
|
||||
pct_diff = 0
|
||||
else:
|
||||
pct_diff = ((price - target) / target) * 100
|
||||
|
||||
result = {
|
||||
"ticker": ticker,
|
||||
"target_price": target,
|
||||
"current_price": price,
|
||||
"currency": currency,
|
||||
"pct_from_target": round(pct_diff, 2),
|
||||
"note": alert.get("note", ""),
|
||||
"set_by": alert.get("set_by", ""),
|
||||
}
|
||||
|
||||
if price <= target:
|
||||
triggered.append(result)
|
||||
# Update triggered count (only once per day to avoid inflation)
|
||||
last_triggered = alert.get("last_triggered")
|
||||
today = now.strftime("%Y-%m-%d")
|
||||
if not last_triggered or not last_triggered.startswith(today):
|
||||
alert["triggered_count"] = alert.get("triggered_count", 0) + 1
|
||||
alert["last_triggered"] = now.isoformat()
|
||||
else:
|
||||
watching.append(result)
|
||||
|
||||
save_alerts(data)
|
||||
|
||||
if args.json:
|
||||
print(json.dumps({"triggered": triggered, "watching": watching}, indent=2))
|
||||
return
|
||||
|
||||
# Translations
|
||||
lang = getattr(args, 'lang', 'en')
|
||||
if lang == "de":
|
||||
labels = {
|
||||
"title": "PREISWARNUNGEN",
|
||||
"in_zone": "IN KAUFZONE",
|
||||
"buy": "KAUFEN!",
|
||||
"target": "Ziel",
|
||||
"watching": "BEOBACHTUNG",
|
||||
"to_target": "noch",
|
||||
"no_data": "Keine Preisdaten für Alerts verfügbar",
|
||||
}
|
||||
else:
|
||||
labels = {
|
||||
"title": "PRICE ALERTS",
|
||||
"in_zone": "IN BUY ZONE",
|
||||
"buy": "BUY SIGNAL",
|
||||
"target": "target",
|
||||
"watching": "WATCHING",
|
||||
"to_target": "to target",
|
||||
"no_data": "No price data available for alerts",
|
||||
}
|
||||
|
||||
# Date header
|
||||
date_str = datetime.now().strftime("%b %d, %Y") if lang == "en" else datetime.now().strftime("%d. %b %Y")
|
||||
print(f"📊 {labels['title']} — {date_str}\n")
|
||||
|
||||
# Human-readable output
|
||||
if triggered:
|
||||
print(f"🟢 {labels['in_zone']}:\n")
|
||||
for t in triggered:
|
||||
target_str = format_price(t["target_price"], t["currency"])
|
||||
current_str = format_price(t["current_price"], t["currency"])
|
||||
note = f'\n "{t["note"]}"' if t.get("note") else ""
|
||||
user = f" — {t['set_by']}" if t.get("set_by") else ""
|
||||
print(f"• {t['ticker']}: {current_str} ({labels['target']}: {target_str}) ← {labels['buy']}{note}{user}")
|
||||
print()
|
||||
|
||||
if watching:
|
||||
print(f"⏳ {labels['watching']}:\n")
|
||||
for w in sorted(watching, key=lambda x: x["pct_from_target"]):
|
||||
target_str = format_price(w["target_price"], w["currency"])
|
||||
current_str = format_price(w["current_price"], w["currency"])
|
||||
print(f"• {w['ticker']}: {current_str} ({labels['target']}: {target_str}) — {labels['to_target']} {abs(w['pct_from_target']):.1f}%")
|
||||
print()
|
||||
|
||||
if not triggered and not watching:
|
||||
print(f"📭 {labels['no_data']}")
|
||||
|
||||
|
||||
def check_alerts() -> dict:
|
||||
"""
|
||||
Check alerts and return results for briefing integration.
|
||||
Returns: {"triggered": [...], "watching": [...]}
|
||||
"""
|
||||
data = load_alerts()
|
||||
alerts = data.get("alerts", [])
|
||||
|
||||
if not alerts:
|
||||
return {"triggered": [], "watching": []}
|
||||
|
||||
now = datetime.now()
|
||||
active_alerts = [
|
||||
a for a in alerts
|
||||
if not a.get("snooze_until") or datetime.fromisoformat(a["snooze_until"]) <= now
|
||||
]
|
||||
|
||||
if not active_alerts:
|
||||
return {"triggered": [], "watching": []}
|
||||
|
||||
tickers = [a["ticker"] for a in active_alerts]
|
||||
quotes = get_fetch_market_data()(tickers, timeout=30)
|
||||
|
||||
triggered = []
|
||||
watching = []
|
||||
|
||||
for alert in active_alerts:
|
||||
ticker = alert["ticker"]
|
||||
target = alert["target_price"]
|
||||
currency = alert.get("currency", "USD")
|
||||
|
||||
quote = quotes.get(ticker, {})
|
||||
price = quote.get("price")
|
||||
|
||||
if price is None:
|
||||
continue
|
||||
|
||||
# Divide-by-zero protection
|
||||
if target == 0:
|
||||
pct_diff = 0
|
||||
else:
|
||||
pct_diff = ((price - target) / target) * 100
|
||||
|
||||
result = {
|
||||
"ticker": ticker,
|
||||
"target_price": target,
|
||||
"current_price": price,
|
||||
"currency": currency,
|
||||
"pct_from_target": round(pct_diff, 2),
|
||||
"note": alert.get("note", ""),
|
||||
"set_by": alert.get("set_by", ""),
|
||||
}
|
||||
|
||||
if price <= target:
|
||||
triggered.append(result)
|
||||
# Update triggered count (only once per day to avoid inflation)
|
||||
last_triggered = alert.get("last_triggered")
|
||||
today = now.strftime("%Y-%m-%d")
|
||||
if not last_triggered or not last_triggered.startswith(today):
|
||||
alert["triggered_count"] = alert.get("triggered_count", 0) + 1
|
||||
alert["last_triggered"] = now.isoformat()
|
||||
else:
|
||||
watching.append(result)
|
||||
|
||||
save_alerts(data)
|
||||
return {"triggered": triggered, "watching": watching}
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Price target alerts")
|
||||
subparsers = parser.add_subparsers(dest="command", required=True)
|
||||
|
||||
# list
|
||||
subparsers.add_parser("list", help="List all alerts")
|
||||
|
||||
# set
|
||||
set_parser = subparsers.add_parser("set", help="Set new alert")
|
||||
set_parser.add_argument("ticker", help="Stock ticker")
|
||||
set_parser.add_argument("target", type=float, help="Target price")
|
||||
set_parser.add_argument("--note", help="Note/reason")
|
||||
set_parser.add_argument("--user", help="Who set the alert")
|
||||
set_parser.add_argument("--currency", default="USD", help="Currency (USD, EUR, JPY, SGD, MXN)")
|
||||
|
||||
# delete
|
||||
del_parser = subparsers.add_parser("delete", help="Delete alert")
|
||||
del_parser.add_argument("ticker", help="Stock ticker")
|
||||
|
||||
# snooze
|
||||
snooze_parser = subparsers.add_parser("snooze", help="Snooze alert")
|
||||
snooze_parser.add_argument("ticker", help="Stock ticker")
|
||||
snooze_parser.add_argument("--days", type=int, default=7, help="Days to snooze")
|
||||
|
||||
# update
|
||||
update_parser = subparsers.add_parser("update", help="Update alert target")
|
||||
update_parser.add_argument("ticker", help="Stock ticker")
|
||||
update_parser.add_argument("target", type=float, help="New target price")
|
||||
update_parser.add_argument("--note", help="Update note")
|
||||
|
||||
# check
|
||||
check_parser = subparsers.add_parser("check", help="Check alerts against prices")
|
||||
check_parser.add_argument("--json", action="store_true", help="JSON output")
|
||||
check_parser.add_argument("--lang", default="en", help="Output language (en, de)")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.command == "list":
|
||||
cmd_list(args)
|
||||
elif args.command == "set":
|
||||
cmd_set(args)
|
||||
elif args.command == "delete":
|
||||
cmd_delete(args)
|
||||
elif args.command == "snooze":
|
||||
cmd_snooze(args)
|
||||
elif args.command == "update":
|
||||
cmd_update(args)
|
||||
elif args.command == "check":
|
||||
cmd_check(args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
170
scripts/briefing.py
Normal file
170
scripts/briefing.py
Normal file
@@ -0,0 +1,170 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Briefing Generator - Main entry point for market briefings.
|
||||
Generates and optionally sends to WhatsApp group.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
from utils import ensure_venv
|
||||
|
||||
SCRIPT_DIR = Path(__file__).parent
|
||||
|
||||
ensure_venv()
|
||||
|
||||
|
||||
def send_to_whatsapp(message: str, group_name: str | None = None):
|
||||
"""Send message to WhatsApp group via openclaw message tool."""
|
||||
if not group_name:
|
||||
group_name = os.environ.get('FINANCE_NEWS_TARGET', '')
|
||||
if not group_name:
|
||||
print("❌ No target specified. Set FINANCE_NEWS_TARGET env var or use --group", file=sys.stderr)
|
||||
return False
|
||||
# Use openclaw message tool
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[
|
||||
'openclaw', 'message', 'send',
|
||||
'--channel', 'whatsapp',
|
||||
'--target', group_name,
|
||||
'--message', message
|
||||
],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=30
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
print(f"✅ Sent to WhatsApp group: {group_name}", file=sys.stderr)
|
||||
return True
|
||||
else:
|
||||
print(f"⚠️ WhatsApp send failed: {result.stderr}", file=sys.stderr)
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ WhatsApp error: {e}", file=sys.stderr)
|
||||
return False
|
||||
|
||||
|
||||
def generate_and_send(args):
|
||||
"""Generate briefing and optionally send to WhatsApp."""
|
||||
|
||||
# Determine briefing type based on current time or args
|
||||
if args.time:
|
||||
briefing_time = args.time
|
||||
else:
|
||||
hour = datetime.now().hour
|
||||
briefing_time = 'morning' if hour < 12 else 'evening'
|
||||
|
||||
# Generate the briefing
|
||||
cmd = [
|
||||
sys.executable, SCRIPT_DIR / 'summarize.py',
|
||||
'--time', briefing_time,
|
||||
'--style', args.style,
|
||||
'--lang', args.lang
|
||||
]
|
||||
|
||||
if args.deadline is not None:
|
||||
cmd.extend(['--deadline', str(args.deadline)])
|
||||
|
||||
if args.fast:
|
||||
cmd.append('--fast')
|
||||
|
||||
if args.llm:
|
||||
cmd.append('--llm')
|
||||
cmd.extend(['--model', args.model])
|
||||
|
||||
if args.debug:
|
||||
cmd.append('--debug')
|
||||
|
||||
# Always use JSON for internal processing to handle splits
|
||||
cmd.append('--json')
|
||||
|
||||
print(f"📊 Generating {briefing_time} briefing...", file=sys.stderr)
|
||||
|
||||
timeout = args.deadline if args.deadline is not None else 300
|
||||
timeout = max(1, int(timeout))
|
||||
if args.deadline is not None:
|
||||
timeout = timeout + 5
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
stdin=subprocess.DEVNULL,
|
||||
timeout=timeout
|
||||
)
|
||||
|
||||
if result.returncode != 0:
|
||||
print(f"❌ Briefing generation failed: {result.stderr}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
data = json.loads(result.stdout.strip())
|
||||
except json.JSONDecodeError:
|
||||
# Fallback if not JSON (shouldn't happen with --json)
|
||||
print(f"⚠️ Failed to parse briefing JSON", file=sys.stderr)
|
||||
print(result.stdout)
|
||||
return result.stdout
|
||||
|
||||
# Output handling
|
||||
if args.json:
|
||||
print(json.dumps(data, indent=2))
|
||||
else:
|
||||
# Print for humans
|
||||
if data.get('macro_message'):
|
||||
print(data['macro_message'])
|
||||
if data.get('portfolio_message'):
|
||||
print("\n" + "="*20 + "\n")
|
||||
print(data['portfolio_message'])
|
||||
|
||||
# Send to WhatsApp if requested
|
||||
if args.send and args.group:
|
||||
# Message 1: Macro
|
||||
macro_msg = data.get('macro_message') or data.get('summary', '')
|
||||
if macro_msg:
|
||||
send_to_whatsapp(macro_msg, args.group)
|
||||
|
||||
# Message 2: Portfolio (if exists)
|
||||
portfolio_msg = data.get('portfolio_message')
|
||||
if portfolio_msg:
|
||||
send_to_whatsapp(portfolio_msg, args.group)
|
||||
|
||||
return data.get('macro_message', '')
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Briefing Generator')
|
||||
parser.add_argument('--time', choices=['morning', 'evening'],
|
||||
help='Briefing type (auto-detected if not specified)')
|
||||
parser.add_argument('--style', choices=['briefing', 'analysis', 'headlines'],
|
||||
default='briefing', help='Summary style')
|
||||
parser.add_argument('--lang', choices=['en', 'de'], default='en',
|
||||
help='Output language')
|
||||
parser.add_argument('--send', action='store_true',
|
||||
help='Send to WhatsApp group')
|
||||
parser.add_argument('--group', default=os.environ.get('FINANCE_NEWS_TARGET', ''),
|
||||
help='WhatsApp group name or JID (default: FINANCE_NEWS_TARGET env var)')
|
||||
parser.add_argument('--json', action='store_true',
|
||||
help='Output as JSON')
|
||||
parser.add_argument('--deadline', type=int, default=None,
|
||||
help='Overall deadline in seconds')
|
||||
parser.add_argument('--llm', action='store_true', help='Use LLM summary')
|
||||
parser.add_argument('--model', choices=['claude', 'minimax', 'gemini'],
|
||||
default='claude', help='LLM model (only with --llm)')
|
||||
parser.add_argument('--fast', action='store_true',
|
||||
help='Use fast mode (shorter timeouts, fewer items)')
|
||||
parser.add_argument('--debug', action='store_true',
|
||||
help='Write debug log with sources')
|
||||
|
||||
args = parser.parse_args()
|
||||
generate_and_send(args)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
614
scripts/earnings.py
Normal file
614
scripts/earnings.py
Normal file
@@ -0,0 +1,614 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Earnings Calendar - Track earnings dates for portfolio stocks.
|
||||
|
||||
Features:
|
||||
- Fetch earnings dates from FMP API
|
||||
- Show upcoming earnings in daily briefing
|
||||
- Alert 24h before earnings release
|
||||
- Cache results to avoid API spam
|
||||
|
||||
Usage:
|
||||
earnings.py list # Show all upcoming earnings
|
||||
earnings.py check # Check what's reporting today/this week
|
||||
earnings.py refresh # Force refresh earnings data
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import csv
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
from urllib.request import urlopen, Request
|
||||
from urllib.error import URLError, HTTPError
|
||||
|
||||
# Paths
|
||||
SCRIPT_DIR = Path(__file__).parent
|
||||
CONFIG_DIR = SCRIPT_DIR.parent / "config"
|
||||
CACHE_DIR = SCRIPT_DIR.parent / "cache"
|
||||
PORTFOLIO_FILE = CONFIG_DIR / "portfolio.csv"
|
||||
EARNINGS_CACHE = CACHE_DIR / "earnings_calendar.json"
|
||||
MANUAL_EARNINGS = CONFIG_DIR / "manual_earnings.json" # For JP/other stocks not in Finnhub
|
||||
|
||||
# OpenBB binary path
|
||||
OPENBB_BINARY = None
|
||||
try:
|
||||
env_path = os.environ.get('OPENBB_QUOTE_BIN')
|
||||
if env_path and os.path.isfile(env_path) and os.access(env_path, os.X_OK):
|
||||
OPENBB_BINARY = env_path
|
||||
else:
|
||||
OPENBB_BINARY = shutil.which('openbb-quote')
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# API Keys
|
||||
def get_fmp_key() -> str:
|
||||
"""Get FMP API key from environment or .env file."""
|
||||
key = os.environ.get("FMP_API_KEY", "")
|
||||
if not key:
|
||||
env_file = Path.home() / ".openclaw" / ".env"
|
||||
if env_file.exists():
|
||||
for line in env_file.read_text().splitlines():
|
||||
if line.startswith("FMP_API_KEY="):
|
||||
key = line.split("=", 1)[1].strip()
|
||||
break
|
||||
return key
|
||||
|
||||
|
||||
def load_portfolio() -> list[dict]:
|
||||
"""Load portfolio from CSV."""
|
||||
if not PORTFOLIO_FILE.exists():
|
||||
return []
|
||||
with open(PORTFOLIO_FILE, 'r') as f:
|
||||
reader = csv.DictReader(f)
|
||||
return list(reader)
|
||||
|
||||
|
||||
def load_earnings_cache() -> dict:
|
||||
"""Load cached earnings data."""
|
||||
if EARNINGS_CACHE.exists():
|
||||
try:
|
||||
return json.loads(EARNINGS_CACHE.read_text())
|
||||
except Exception:
|
||||
pass
|
||||
return {"last_updated": None, "earnings": {}}
|
||||
|
||||
|
||||
def load_manual_earnings() -> dict:
|
||||
"""
|
||||
Load manually-entered earnings dates (for JP stocks not in Finnhub).
|
||||
Format: {"6857.T": {"date": "2026-01-30", "time": "amc", "note": "Q3 FY2025"}, ...}
|
||||
"""
|
||||
if MANUAL_EARNINGS.exists():
|
||||
try:
|
||||
data = json.loads(MANUAL_EARNINGS.read_text())
|
||||
# Filter out metadata keys (starting with _)
|
||||
return {k: v for k, v in data.items() if not k.startswith("_") and isinstance(v, dict)}
|
||||
except Exception:
|
||||
pass
|
||||
return {}
|
||||
|
||||
|
||||
def save_earnings_cache(data: dict):
|
||||
"""Save earnings data to cache."""
|
||||
CACHE_DIR.mkdir(exist_ok=True)
|
||||
EARNINGS_CACHE.write_text(json.dumps(data, indent=2, default=str))
|
||||
|
||||
|
||||
def get_finnhub_key() -> str:
|
||||
"""Get Finnhub API key from environment or .env file."""
|
||||
key = os.environ.get("FINNHUB_API_KEY", "")
|
||||
if not key:
|
||||
env_file = Path.home() / ".openclaw" / ".env"
|
||||
if env_file.exists():
|
||||
for line in env_file.read_text().splitlines():
|
||||
if line.startswith("FINNHUB_API_KEY="):
|
||||
key = line.split("=", 1)[1].strip()
|
||||
break
|
||||
return key
|
||||
|
||||
|
||||
def fetch_all_earnings_finnhub(days_ahead: int = 60) -> dict:
|
||||
"""
|
||||
Fetch all earnings for the next N days from Finnhub.
|
||||
Returns dict keyed by symbol: {"AAPL": {...}, ...}
|
||||
"""
|
||||
finnhub_key = get_finnhub_key()
|
||||
if not finnhub_key:
|
||||
return {}
|
||||
|
||||
from_date = datetime.now().strftime("%Y-%m-%d")
|
||||
to_date = (datetime.now() + timedelta(days=days_ahead)).strftime("%Y-%m-%d")
|
||||
|
||||
url = f"https://finnhub.io/api/v1/calendar/earnings?from={from_date}&to={to_date}&token={finnhub_key}"
|
||||
|
||||
try:
|
||||
req = Request(url, headers={"User-Agent": "finance-news/1.0"})
|
||||
with urlopen(req, timeout=30) as resp:
|
||||
data = json.loads(resp.read().decode("utf-8"))
|
||||
|
||||
earnings_by_symbol = {}
|
||||
for entry in data.get("earningsCalendar", []):
|
||||
symbol = entry.get("symbol")
|
||||
if symbol:
|
||||
earnings_by_symbol[symbol] = {
|
||||
"date": entry.get("date"),
|
||||
"time": entry.get("hour", ""), # bmo/amc
|
||||
"eps_estimate": entry.get("epsEstimate"),
|
||||
"revenue_estimate": entry.get("revenueEstimate"),
|
||||
"quarter": entry.get("quarter"),
|
||||
"year": entry.get("year"),
|
||||
}
|
||||
return earnings_by_symbol
|
||||
except Exception as e:
|
||||
print(f"❌ Finnhub error: {e}", file=sys.stderr)
|
||||
return {}
|
||||
|
||||
|
||||
def normalize_ticker_for_lookup(ticker: str) -> list[str]:
|
||||
"""
|
||||
Convert portfolio ticker to possible Finnhub symbols.
|
||||
Returns list of possible formats to try.
|
||||
"""
|
||||
variants = [ticker]
|
||||
|
||||
# Japanese stocks: 6857.T -> try 6857
|
||||
if ticker.endswith('.T'):
|
||||
base = ticker.replace('.T', '')
|
||||
variants.extend([base, f"{base}.T"])
|
||||
|
||||
# Singapore stocks: D05.SI -> try D05
|
||||
elif ticker.endswith('.SI'):
|
||||
base = ticker.replace('.SI', '')
|
||||
variants.extend([base, f"{base}.SI"])
|
||||
|
||||
return variants
|
||||
|
||||
|
||||
def fetch_earnings_for_portfolio(portfolio: list[dict]) -> dict:
|
||||
"""
|
||||
Fetch earnings dates for portfolio stocks using Finnhub bulk API.
|
||||
More efficient than per-ticker calls.
|
||||
"""
|
||||
# Get all earnings for next 60 days
|
||||
all_earnings = fetch_all_earnings_finnhub(days_ahead=60)
|
||||
|
||||
if not all_earnings:
|
||||
return {}
|
||||
|
||||
# Match portfolio tickers to earnings data
|
||||
results = {}
|
||||
for stock in portfolio:
|
||||
ticker = stock["symbol"]
|
||||
variants = normalize_ticker_for_lookup(ticker)
|
||||
|
||||
for variant in variants:
|
||||
if variant in all_earnings:
|
||||
results[ticker] = all_earnings[variant]
|
||||
break
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def refresh_earnings(portfolio: list[dict], force: bool = False) -> dict:
|
||||
"""Refresh earnings data for all portfolio stocks."""
|
||||
finnhub_key = get_finnhub_key()
|
||||
if not finnhub_key:
|
||||
print("❌ FINNHUB_API_KEY not found", file=sys.stderr)
|
||||
return {}
|
||||
|
||||
cache = load_earnings_cache()
|
||||
|
||||
# Check if cache is fresh (< 6 hours old)
|
||||
if not force and cache.get("last_updated"):
|
||||
try:
|
||||
last = datetime.fromisoformat(cache["last_updated"])
|
||||
if datetime.now() - last < timedelta(hours=6):
|
||||
print(f"📦 Using cached data (updated {last.strftime('%H:%M')})")
|
||||
return cache
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
print(f"🔄 Fetching earnings calendar from Finnhub...")
|
||||
|
||||
# Use bulk fetch - much more efficient
|
||||
earnings = fetch_earnings_for_portfolio(portfolio)
|
||||
|
||||
# Merge manual earnings (for JP stocks not in Finnhub)
|
||||
manual = load_manual_earnings()
|
||||
if manual:
|
||||
print(f"📝 Merging {len(manual)} manual entries...")
|
||||
for ticker, data in manual.items():
|
||||
if ticker not in earnings: # Manual data fills gaps
|
||||
earnings[ticker] = data
|
||||
|
||||
found = len(earnings)
|
||||
total = len(portfolio)
|
||||
print(f"✅ Found earnings data for {found}/{total} stocks")
|
||||
|
||||
if earnings:
|
||||
for ticker, data in sorted(earnings.items(), key=lambda x: x[1].get("date", "")):
|
||||
print(f" • {ticker}: {data.get('date', '?')}")
|
||||
|
||||
cache = {
|
||||
"last_updated": datetime.now().isoformat(),
|
||||
"earnings": earnings
|
||||
}
|
||||
save_earnings_cache(cache)
|
||||
|
||||
return cache
|
||||
|
||||
|
||||
def list_earnings(args):
|
||||
"""List all upcoming earnings for portfolio."""
|
||||
portfolio = load_portfolio()
|
||||
if not portfolio:
|
||||
print("📂 Portfolio empty")
|
||||
return
|
||||
|
||||
cache = refresh_earnings(portfolio, force=args.refresh)
|
||||
earnings = cache.get("earnings", {})
|
||||
|
||||
if not earnings:
|
||||
print("\n❌ No earnings dates found")
|
||||
return
|
||||
|
||||
# Sort by date
|
||||
sorted_earnings = sorted(
|
||||
[(ticker, data) for ticker, data in earnings.items() if data.get("date")],
|
||||
key=lambda x: x[1]["date"]
|
||||
)
|
||||
|
||||
print(f"\n📅 Upcoming Earnings ({len(sorted_earnings)} stocks)\n")
|
||||
|
||||
today = datetime.now().date()
|
||||
|
||||
for ticker, data in sorted_earnings:
|
||||
date_str = data["date"]
|
||||
try:
|
||||
ed = datetime.strptime(date_str, "%Y-%m-%d").date()
|
||||
days_until = (ed - today).days
|
||||
|
||||
# Emoji based on timing
|
||||
if days_until < 0:
|
||||
emoji = "✅" # Past
|
||||
timing = f"{-days_until}d ago"
|
||||
elif days_until == 0:
|
||||
emoji = "🔴" # Today!
|
||||
timing = "TODAY"
|
||||
elif days_until == 1:
|
||||
emoji = "🟡" # Tomorrow
|
||||
timing = "TOMORROW"
|
||||
elif days_until <= 7:
|
||||
emoji = "🟠" # This week
|
||||
timing = f"in {days_until}d"
|
||||
else:
|
||||
emoji = "⚪" # Later
|
||||
timing = f"in {days_until}d"
|
||||
|
||||
# Time of day
|
||||
time_str = ""
|
||||
if data.get("time") == "bmo":
|
||||
time_str = " (pre-market)"
|
||||
elif data.get("time") == "amc":
|
||||
time_str = " (after-close)"
|
||||
|
||||
# EPS estimate
|
||||
eps_str = ""
|
||||
if data.get("eps_estimate"):
|
||||
eps_str = f" | Est: ${data['eps_estimate']:.2f}"
|
||||
|
||||
# Stock name from portfolio
|
||||
stock_name = next((s["name"] for s in portfolio if s["symbol"] == ticker), ticker)
|
||||
|
||||
print(f"{emoji} {date_str} ({timing}): **{ticker}** — {stock_name}{time_str}{eps_str}")
|
||||
|
||||
except ValueError:
|
||||
print(f"⚪ {date_str}: {ticker}")
|
||||
|
||||
print()
|
||||
|
||||
|
||||
def check_earnings(args):
|
||||
"""Check earnings for today and this week (briefing format)."""
|
||||
portfolio = load_portfolio()
|
||||
if not portfolio:
|
||||
return
|
||||
|
||||
cache = load_earnings_cache()
|
||||
|
||||
# Auto-refresh if cache is stale
|
||||
if not cache.get("last_updated"):
|
||||
cache = refresh_earnings(portfolio, force=False)
|
||||
else:
|
||||
try:
|
||||
last = datetime.fromisoformat(cache["last_updated"])
|
||||
if datetime.now() - last > timedelta(hours=12):
|
||||
cache = refresh_earnings(portfolio, force=False)
|
||||
except Exception:
|
||||
cache = refresh_earnings(portfolio, force=False)
|
||||
|
||||
earnings = cache.get("earnings", {})
|
||||
if not earnings:
|
||||
return
|
||||
|
||||
today = datetime.now().date()
|
||||
week_only = getattr(args, 'week', False)
|
||||
|
||||
# For weekly mode (Sunday cron), show Mon-Fri of upcoming week
|
||||
# Calculation: weekday() returns 0=Mon, 6=Sun. (7 - weekday) % 7 gives days until next Monday.
|
||||
# Special case: if today is Monday (result=0), we want next Monday (7 days), not today.
|
||||
if week_only:
|
||||
days_until_monday = (7 - today.weekday()) % 7
|
||||
if days_until_monday == 0 and today.weekday() != 0:
|
||||
days_until_monday = 7
|
||||
week_start = today + timedelta(days=days_until_monday)
|
||||
week_end = week_start + timedelta(days=4) # Mon-Fri
|
||||
else:
|
||||
week_end = today + timedelta(days=7)
|
||||
|
||||
today_list = []
|
||||
week_list = []
|
||||
|
||||
for ticker, data in earnings.items():
|
||||
if not data.get("date"):
|
||||
continue
|
||||
try:
|
||||
ed = datetime.strptime(data["date"], "%Y-%m-%d").date()
|
||||
stock = next((s for s in portfolio if s["symbol"] == ticker), None)
|
||||
name = stock["name"] if stock else ticker
|
||||
category = stock.get("category", "") if stock else ""
|
||||
|
||||
entry = {
|
||||
"ticker": ticker,
|
||||
"name": name,
|
||||
"date": ed,
|
||||
"time": data.get("time", ""),
|
||||
"eps_estimate": data.get("eps_estimate"),
|
||||
"category": category,
|
||||
}
|
||||
|
||||
if week_only:
|
||||
# Weekly mode: only show week range
|
||||
if week_start <= ed <= week_end:
|
||||
week_list.append(entry)
|
||||
else:
|
||||
# Daily mode: today + this week
|
||||
if ed == today:
|
||||
today_list.append(entry)
|
||||
elif today < ed <= week_end:
|
||||
week_list.append(entry)
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
# Handle JSON output
|
||||
if getattr(args, 'json', False):
|
||||
if week_only:
|
||||
result = {
|
||||
"week_start": week_start.isoformat(),
|
||||
"week_end": week_end.isoformat(),
|
||||
"earnings": [
|
||||
{
|
||||
"ticker": e["ticker"],
|
||||
"name": e["name"],
|
||||
"date": e["date"].isoformat(),
|
||||
"time": e["time"],
|
||||
"eps_estimate": e.get("eps_estimate"),
|
||||
"category": e.get("category", ""),
|
||||
}
|
||||
for e in sorted(week_list, key=lambda x: x["date"])
|
||||
],
|
||||
}
|
||||
else:
|
||||
result = {
|
||||
"today": [
|
||||
{
|
||||
"ticker": e["ticker"],
|
||||
"name": e["name"],
|
||||
"date": e["date"].isoformat(),
|
||||
"time": e["time"],
|
||||
"eps_estimate": e.get("eps_estimate"),
|
||||
"category": e.get("category", ""),
|
||||
}
|
||||
for e in sorted(today_list, key=lambda x: x.get("time", "zzz"))
|
||||
],
|
||||
"this_week": [
|
||||
{
|
||||
"ticker": e["ticker"],
|
||||
"name": e["name"],
|
||||
"date": e["date"].isoformat(),
|
||||
"time": e["time"],
|
||||
"eps_estimate": e.get("eps_estimate"),
|
||||
"category": e.get("category", ""),
|
||||
}
|
||||
for e in sorted(week_list, key=lambda x: x["date"])
|
||||
],
|
||||
}
|
||||
print(json.dumps(result, indent=2))
|
||||
return
|
||||
|
||||
# Translations
|
||||
lang = getattr(args, 'lang', 'en')
|
||||
if lang == "de":
|
||||
labels = {
|
||||
"today": "EARNINGS HEUTE",
|
||||
"week": "EARNINGS DIESE WOCHE",
|
||||
"week_preview": "EARNINGS NÄCHSTE WOCHE",
|
||||
"pre": "vor Börseneröffnung",
|
||||
"post": "nach Börsenschluss",
|
||||
"pre_short": "vor",
|
||||
"post_short": "nach",
|
||||
"est": "Erw",
|
||||
"none": "Keine Earnings diese Woche",
|
||||
"none_week": "Keine Earnings nächste Woche",
|
||||
}
|
||||
else:
|
||||
labels = {
|
||||
"today": "EARNINGS TODAY",
|
||||
"week": "EARNINGS THIS WEEK",
|
||||
"week_preview": "EARNINGS NEXT WEEK",
|
||||
"pre": "pre-market",
|
||||
"post": "after-close",
|
||||
"pre_short": "pre",
|
||||
"post_short": "post",
|
||||
"est": "Est",
|
||||
"none": "No earnings this week",
|
||||
"none_week": "No earnings next week",
|
||||
}
|
||||
|
||||
# Date header
|
||||
date_str = datetime.now().strftime("%b %d, %Y") if lang == "en" else datetime.now().strftime("%d. %b %Y")
|
||||
|
||||
# Output for briefing
|
||||
output = []
|
||||
|
||||
# Daily mode: show today's earnings
|
||||
if not week_only and today_list:
|
||||
output.append(f"📅 {labels['today']} — {date_str}\n")
|
||||
for e in sorted(today_list, key=lambda x: x.get("time", "zzz")):
|
||||
time_str = f" ({labels['pre']})" if e["time"] == "bmo" else f" ({labels['post']})" if e["time"] == "amc" else ""
|
||||
eps_str = f" — {labels['est']}: ${e['eps_estimate']:.2f}" if e.get("eps_estimate") else ""
|
||||
output.append(f"• {e['ticker']} — {e['name']}{time_str}{eps_str}")
|
||||
output.append("")
|
||||
|
||||
if week_list:
|
||||
# Use different header for weekly preview mode
|
||||
week_label = labels['week_preview'] if week_only else labels['week']
|
||||
if week_only:
|
||||
# Show date range for weekly preview
|
||||
week_range = f"{week_start.strftime('%b %d')} - {week_end.strftime('%b %d')}"
|
||||
output.append(f"📅 {week_label} ({week_range})\n")
|
||||
else:
|
||||
output.append(f"📅 {week_label}\n")
|
||||
for e in sorted(week_list, key=lambda x: x["date"]):
|
||||
day_name = e["date"].strftime("%a %d.%m")
|
||||
time_str = f" ({labels['pre_short']})" if e["time"] == "bmo" else f" ({labels['post_short']})" if e["time"] == "amc" else ""
|
||||
output.append(f"• {day_name}: {e['ticker']} — {e['name']}{time_str}")
|
||||
output.append("")
|
||||
|
||||
if output:
|
||||
print("\n".join(output))
|
||||
else:
|
||||
if args.verbose:
|
||||
no_earnings_label = labels['none_week'] if week_only else labels['none']
|
||||
print(f"📅 {no_earnings_label}")
|
||||
|
||||
|
||||
def get_briefing_section() -> str:
|
||||
"""Get earnings section for daily briefing (called by briefing.py)."""
|
||||
from io import StringIO
|
||||
import contextlib
|
||||
|
||||
# Capture check output
|
||||
class Args:
|
||||
verbose = False
|
||||
|
||||
f = StringIO()
|
||||
with contextlib.redirect_stdout(f):
|
||||
check_earnings(Args())
|
||||
|
||||
return f.getvalue()
|
||||
|
||||
|
||||
def get_earnings_context(symbols: list[str]) -> list[dict]:
|
||||
"""
|
||||
Get recent earnings data (beats/misses) for symbols using OpenBB.
|
||||
|
||||
Returns list of dicts with: symbol, eps_actual, eps_estimate, surprise, revenue_actual, revenue_estimate
|
||||
"""
|
||||
if not OPENBB_BINARY:
|
||||
return []
|
||||
|
||||
results = []
|
||||
for symbol in symbols[:10]: # Limit to 10 symbols
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[OPENBB_BINARY, symbol, '--earnings'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=30
|
||||
)
|
||||
if result.returncode == 0:
|
||||
try:
|
||||
data = json.loads(result.stdout)
|
||||
if isinstance(data, list) and data:
|
||||
results.append({
|
||||
'symbol': symbol,
|
||||
'earnings': data[0] if isinstance(data[0], dict) else {}
|
||||
})
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
return results
|
||||
|
||||
|
||||
def get_analyst_ratings(symbols: list[str]) -> list[dict]:
|
||||
"""
|
||||
Get analyst upgrades/downgrades for symbols using OpenBB.
|
||||
|
||||
Returns list of dicts with: symbol, rating, target_price, firm, direction
|
||||
"""
|
||||
if not OPENBB_BINARY:
|
||||
return []
|
||||
|
||||
results = []
|
||||
for symbol in symbols[:10]: # Limit to 10 symbols
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[OPENBB_BINARY, symbol, '--rating'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=30
|
||||
)
|
||||
if result.returncode == 0:
|
||||
try:
|
||||
data = json.loads(result.stdout)
|
||||
if isinstance(data, list) and data:
|
||||
results.append({
|
||||
'symbol': symbol,
|
||||
'rating': data[0] if isinstance(data[0], dict) else {}
|
||||
})
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
return results
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Earnings Calendar Tracker")
|
||||
subparsers = parser.add_subparsers(dest="command", help="Commands")
|
||||
|
||||
# list command
|
||||
list_parser = subparsers.add_parser("list", help="List all upcoming earnings")
|
||||
list_parser.add_argument("--refresh", "-r", action="store_true", help="Force refresh")
|
||||
list_parser.set_defaults(func=list_earnings)
|
||||
|
||||
# check command
|
||||
check_parser = subparsers.add_parser("check", help="Check today/this week")
|
||||
check_parser.add_argument("--verbose", "-v", action="store_true")
|
||||
check_parser.add_argument("--json", action="store_true", help="JSON output")
|
||||
check_parser.add_argument("--lang", default="en", help="Output language (en, de)")
|
||||
check_parser.add_argument("--week", action="store_true", help="Show full week preview (for weekly cron)")
|
||||
check_parser.set_defaults(func=check_earnings)
|
||||
|
||||
# refresh command
|
||||
refresh_parser = subparsers.add_parser("refresh", help="Force refresh all data")
|
||||
refresh_parser.set_defaults(func=lambda a: refresh_earnings(load_portfolio(), force=True))
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.command:
|
||||
parser.print_help()
|
||||
return
|
||||
|
||||
args.func(args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
1126
scripts/fetch_news.py
Normal file
1126
scripts/fetch_news.py
Normal file
File diff suppressed because it is too large
Load Diff
317
scripts/portfolio.py
Normal file
317
scripts/portfolio.py
Normal file
@@ -0,0 +1,317 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Portfolio Manager - CRUD operations for stock watchlist.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import csv
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
PORTFOLIO_FILE = Path(__file__).parent.parent / "config" / "portfolio.csv"
|
||||
REQUIRED_COLUMNS = ['symbol', 'name']
|
||||
DEFAULT_COLUMNS = ['symbol', 'name', 'category', 'notes', 'type']
|
||||
|
||||
|
||||
def validate_portfolio_csv(path: Path) -> tuple[bool, list[str]]:
|
||||
"""
|
||||
Validate portfolio CSV file for common issues.
|
||||
|
||||
Returns:
|
||||
Tuple of (is_valid, list of warnings)
|
||||
"""
|
||||
warnings = []
|
||||
|
||||
if not path.exists():
|
||||
return True, warnings
|
||||
|
||||
try:
|
||||
with open(path, 'r', encoding='utf-8') as f:
|
||||
# Check for encoding issues
|
||||
content = f.read()
|
||||
|
||||
with open(path, 'r', encoding='utf-8') as f:
|
||||
reader = csv.DictReader(f)
|
||||
|
||||
# Check required columns
|
||||
if reader.fieldnames is None:
|
||||
warnings.append("CSV appears to be empty")
|
||||
return False, warnings
|
||||
|
||||
missing_cols = set(REQUIRED_COLUMNS) - set(reader.fieldnames or [])
|
||||
if missing_cols:
|
||||
warnings.append(f"Missing required columns: {', '.join(missing_cols)}")
|
||||
|
||||
# Check for duplicate symbols
|
||||
symbols = []
|
||||
for row in reader:
|
||||
symbol = row.get('symbol', '').strip().upper()
|
||||
if symbol:
|
||||
symbols.append(symbol)
|
||||
|
||||
duplicates = [s for s in set(symbols) if symbols.count(s) > 1]
|
||||
if duplicates:
|
||||
warnings.append(f"Duplicate symbols found: {', '.join(duplicates)}")
|
||||
|
||||
except UnicodeDecodeError:
|
||||
warnings.append("File encoding issue - try saving as UTF-8")
|
||||
except Exception as e:
|
||||
warnings.append(f"Error reading portfolio: {e}")
|
||||
return False, warnings
|
||||
|
||||
return True, warnings
|
||||
|
||||
|
||||
def load_portfolio() -> list[dict]:
|
||||
"""Load portfolio from CSV with validation."""
|
||||
if not PORTFOLIO_FILE.exists():
|
||||
return []
|
||||
|
||||
# Validate first
|
||||
is_valid, warnings = validate_portfolio_csv(PORTFOLIO_FILE)
|
||||
for warning in warnings:
|
||||
print(f"⚠️ Portfolio warning: {warning}", file=sys.stderr)
|
||||
|
||||
if not is_valid:
|
||||
print("⚠️ Portfolio has errors - returning empty", file=sys.stderr)
|
||||
return []
|
||||
|
||||
try:
|
||||
with open(PORTFOLIO_FILE, 'r', encoding='utf-8') as f:
|
||||
reader = csv.DictReader(f)
|
||||
|
||||
# Normalize data
|
||||
portfolio = []
|
||||
seen_symbols = set()
|
||||
|
||||
for row in reader:
|
||||
symbol = row.get('symbol', '').strip().upper()
|
||||
if not symbol:
|
||||
continue
|
||||
|
||||
# Skip duplicates (keep first occurrence)
|
||||
if symbol in seen_symbols:
|
||||
continue
|
||||
seen_symbols.add(symbol)
|
||||
|
||||
portfolio.append({
|
||||
'symbol': symbol,
|
||||
'name': row.get('name', symbol) or symbol,
|
||||
'category': row.get('category', '') or '',
|
||||
'notes': row.get('notes', '') or '',
|
||||
'type': row.get('type', 'Watchlist') or 'Watchlist'
|
||||
})
|
||||
|
||||
return portfolio
|
||||
|
||||
except Exception as e:
|
||||
print(f"⚠️ Error loading portfolio: {e}", file=sys.stderr)
|
||||
return []
|
||||
|
||||
|
||||
def save_portfolio(portfolio: list[dict]):
|
||||
"""Save portfolio to CSV."""
|
||||
if not portfolio:
|
||||
PORTFOLIO_FILE.write_text("symbol,name,category,notes,type\n")
|
||||
return
|
||||
|
||||
with open(PORTFOLIO_FILE, 'w', newline='') as f:
|
||||
writer = csv.DictWriter(f, fieldnames=['symbol', 'name', 'category', 'notes', 'type'])
|
||||
writer.writeheader()
|
||||
writer.writerows(portfolio)
|
||||
|
||||
|
||||
def list_portfolio(args):
|
||||
"""List all stocks in portfolio."""
|
||||
portfolio = load_portfolio()
|
||||
|
||||
if not portfolio:
|
||||
print("📂 Portfolio is empty. Use 'portfolio add <SYMBOL>' to add stocks.")
|
||||
return
|
||||
|
||||
print(f"\n📊 Portfolio ({len(portfolio)} stocks)\n")
|
||||
|
||||
# Group by Type then Category
|
||||
by_type = {}
|
||||
for stock in portfolio:
|
||||
t = stock.get('type', 'Watchlist') or 'Watchlist'
|
||||
if t not in by_type:
|
||||
by_type[t] = []
|
||||
by_type[t].append(stock)
|
||||
|
||||
for t, type_stocks in by_type.items():
|
||||
print(f"# {t}")
|
||||
categories = {}
|
||||
for stock in type_stocks:
|
||||
cat = stock.get('category', 'Other') or 'Other'
|
||||
if cat not in categories:
|
||||
categories[cat] = []
|
||||
categories[cat].append(stock)
|
||||
|
||||
for cat, stocks in categories.items():
|
||||
print(f"### {cat}")
|
||||
for s in stocks:
|
||||
notes = f" — {s['notes']}" if s.get('notes') else ""
|
||||
print(f" • {s['symbol']}: {s['name']}{notes}")
|
||||
print()
|
||||
|
||||
|
||||
def add_stock(args):
|
||||
"""Add a stock to portfolio."""
|
||||
portfolio = load_portfolio()
|
||||
|
||||
# Check if already exists
|
||||
if any(s['symbol'].upper() == args.symbol.upper() for s in portfolio):
|
||||
print(f"⚠️ {args.symbol.upper()} already in portfolio")
|
||||
return
|
||||
|
||||
new_stock = {
|
||||
'symbol': args.symbol.upper(),
|
||||
'name': args.name or args.symbol.upper(),
|
||||
'category': args.category or '',
|
||||
'notes': args.notes or '',
|
||||
'type': args.type
|
||||
}
|
||||
|
||||
portfolio.append(new_stock)
|
||||
save_portfolio(portfolio)
|
||||
print(f"✅ Added {args.symbol.upper()} to portfolio ({args.type})")
|
||||
|
||||
|
||||
def remove_stock(args):
|
||||
"""Remove a stock from portfolio."""
|
||||
portfolio = load_portfolio()
|
||||
|
||||
original_len = len(portfolio)
|
||||
portfolio = [s for s in portfolio if s['symbol'].upper() != args.symbol.upper()]
|
||||
|
||||
if len(portfolio) == original_len:
|
||||
print(f"⚠️ {args.symbol.upper()} not found in portfolio")
|
||||
return
|
||||
|
||||
save_portfolio(portfolio)
|
||||
print(f"✅ Removed {args.symbol.upper()} from portfolio")
|
||||
|
||||
|
||||
def import_csv(args):
|
||||
"""Import portfolio from external CSV."""
|
||||
import_path = Path(args.file)
|
||||
|
||||
if not import_path.exists():
|
||||
print(f"❌ File not found: {args.file}")
|
||||
sys.exit(1)
|
||||
|
||||
with open(import_path, 'r') as f:
|
||||
reader = csv.DictReader(f)
|
||||
imported = list(reader)
|
||||
|
||||
# Normalize fields
|
||||
normalized = []
|
||||
for row in imported:
|
||||
normalized.append({
|
||||
'symbol': row.get('symbol', row.get('Symbol', row.get('ticker', ''))).upper(),
|
||||
'name': row.get('name', row.get('Name', row.get('company', ''))),
|
||||
'category': row.get('category', row.get('Category', row.get('sector', ''))),
|
||||
'notes': row.get('notes', row.get('Notes', '')),
|
||||
'type': row.get('type', 'Watchlist')
|
||||
})
|
||||
|
||||
save_portfolio(normalized)
|
||||
print(f"✅ Imported {len(normalized)} stocks from {args.file}")
|
||||
|
||||
|
||||
def create_interactive(args):
|
||||
"""Interactive portfolio creation."""
|
||||
print("\n📊 Portfolio Creator\n")
|
||||
print("Enter stocks one per line (format: SYMBOL or SYMBOL,Name,Category)")
|
||||
print("Type 'done' when finished.\n")
|
||||
|
||||
portfolio = []
|
||||
|
||||
while True:
|
||||
try:
|
||||
line = input("> ").strip()
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
break
|
||||
|
||||
if line.lower() == 'done':
|
||||
break
|
||||
|
||||
if not line:
|
||||
continue
|
||||
|
||||
parts = line.split(',')
|
||||
symbol = parts[0].strip().upper()
|
||||
name = parts[1].strip() if len(parts) > 1 else symbol
|
||||
category = parts[2].strip() if len(parts) > 2 else ''
|
||||
|
||||
portfolio.append({
|
||||
'symbol': symbol,
|
||||
'name': name,
|
||||
'category': category,
|
||||
'notes': '',
|
||||
'type': 'Watchlist'
|
||||
})
|
||||
print(f" Added: {symbol}")
|
||||
|
||||
if portfolio:
|
||||
save_portfolio(portfolio)
|
||||
print(f"\n✅ Created portfolio with {len(portfolio)} stocks")
|
||||
else:
|
||||
print("\n⚠️ No stocks added")
|
||||
|
||||
|
||||
def get_symbols(args=None):
|
||||
"""Get list of symbols (for other scripts to use)."""
|
||||
portfolio = load_portfolio()
|
||||
symbols = [s['symbol'] for s in portfolio]
|
||||
|
||||
if args and args.json:
|
||||
import json
|
||||
print(json.dumps(symbols))
|
||||
else:
|
||||
print(','.join(symbols))
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Portfolio Manager')
|
||||
subparsers = parser.add_subparsers(dest='command', required=True)
|
||||
|
||||
# List command
|
||||
list_parser = subparsers.add_parser('list', help='List portfolio')
|
||||
list_parser.set_defaults(func=list_portfolio)
|
||||
|
||||
# Add command
|
||||
add_parser = subparsers.add_parser('add', help='Add stock')
|
||||
add_parser.add_argument('symbol', help='Stock symbol')
|
||||
add_parser.add_argument('--name', help='Company name')
|
||||
add_parser.add_argument('--category', help='Category (e.g., Tech, Finance)')
|
||||
add_parser.add_argument('--notes', help='Notes')
|
||||
add_parser.add_argument('--type', choices=['Holding', 'Watchlist'], default='Watchlist', help='Portfolio type')
|
||||
add_parser.set_defaults(func=add_stock)
|
||||
|
||||
# Remove command
|
||||
remove_parser = subparsers.add_parser('remove', help='Remove stock')
|
||||
remove_parser.add_argument('symbol', help='Stock symbol')
|
||||
remove_parser.set_defaults(func=remove_stock)
|
||||
|
||||
# Import command
|
||||
import_parser = subparsers.add_parser('import', help='Import from CSV')
|
||||
import_parser.add_argument('file', help='CSV file path')
|
||||
import_parser.set_defaults(func=import_csv)
|
||||
|
||||
# Create command
|
||||
create_parser = subparsers.add_parser('create', help='Interactive creation')
|
||||
create_parser.set_defaults(func=create_interactive)
|
||||
|
||||
# Symbols command (for other scripts)
|
||||
symbols_parser = subparsers.add_parser('symbols', help='Get symbols list')
|
||||
symbols_parser.add_argument('--json', action='store_true', help='Output as JSON')
|
||||
symbols_parser.set_defaults(func=get_symbols)
|
||||
|
||||
args = parser.parse_args()
|
||||
args.func(args)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
325
scripts/ranking.py
Normal file
325
scripts/ranking.py
Normal file
@@ -0,0 +1,325 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Deterministic Headline Ranking - Impact-based ranking policy.
|
||||
|
||||
Implements #53: Deterministic impact-based ranking for headline selection.
|
||||
|
||||
Scoring Rubric (weights):
|
||||
- Market Impact (40%): CB decisions, earnings, sanctions, oil spikes
|
||||
- Novelty (20%): New vs recycled news
|
||||
- Breadth (20%): Sector-wide vs single-stock
|
||||
- Credibility (10%): Source reliability
|
||||
- Diversity Bonus (10%): Underrepresented categories
|
||||
|
||||
Output:
|
||||
- MUST_READ: Top 5 stories
|
||||
- SCAN: 3-5 additional stories (if quality threshold met)
|
||||
"""
|
||||
|
||||
import re
|
||||
from datetime import datetime
|
||||
from difflib import SequenceMatcher
|
||||
|
||||
|
||||
# Category keywords for classification
|
||||
CATEGORY_KEYWORDS = {
|
||||
"macro": ["fed", "ecb", "boj", "central bank", "rate", "inflation", "gdp", "unemployment", "treasury", "yield", "bond"],
|
||||
"equities": ["earnings", "revenue", "profit", "eps", "guidance", "beat", "miss", "upgrade", "downgrade", "target"],
|
||||
"geopolitics": ["sanction", "tariff", "war", "conflict", "embargo", "trump", "china", "russia", "ukraine", "iran", "trade war"],
|
||||
"energy": ["oil", "opec", "crude", "gas", "energy", "brent", "wti"],
|
||||
"tech": ["ai", "chip", "semiconductor", "nvidia", "apple", "google", "microsoft", "meta", "amazon"],
|
||||
}
|
||||
|
||||
# Source credibility scores (0-1)
|
||||
SOURCE_CREDIBILITY = {
|
||||
"Wall Street Journal": 0.95,
|
||||
"WSJ": 0.95,
|
||||
"Bloomberg": 0.95,
|
||||
"Reuters": 0.90,
|
||||
"Financial Times": 0.90,
|
||||
"CNBC": 0.80,
|
||||
"Yahoo Finance": 0.70,
|
||||
"MarketWatch": 0.75,
|
||||
"Barron's": 0.85,
|
||||
"Seeking Alpha": 0.60,
|
||||
"Tagesschau": 0.85,
|
||||
"Handelsblatt": 0.80,
|
||||
}
|
||||
|
||||
# Default config
|
||||
DEFAULT_CONFIG = {
|
||||
"dedupe_threshold": 0.7,
|
||||
"must_read_count": 5,
|
||||
"scan_count": 5,
|
||||
"must_read_min_score": 0.4,
|
||||
"scan_min_score": 0.25,
|
||||
"source_cap": 2,
|
||||
"weights": {
|
||||
"market_impact": 0.40,
|
||||
"novelty": 0.20,
|
||||
"breadth": 0.20,
|
||||
"credibility": 0.10,
|
||||
"diversity": 0.10,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def normalize_title(title: str) -> str:
|
||||
"""Normalize title for comparison."""
|
||||
if not title:
|
||||
return ""
|
||||
cleaned = re.sub(r"[^a-z0-9\s]", " ", title.lower())
|
||||
tokens = cleaned.split()
|
||||
return " ".join(tokens)
|
||||
|
||||
|
||||
def title_similarity(a: str, b: str) -> float:
|
||||
"""Calculate title similarity using SequenceMatcher."""
|
||||
if not a or not b:
|
||||
return 0.0
|
||||
return SequenceMatcher(None, normalize_title(a), normalize_title(b)).ratio()
|
||||
|
||||
|
||||
def deduplicate_headlines(headlines: list[dict], threshold: float = 0.7) -> list[dict]:
|
||||
"""Remove duplicate headlines by title similarity."""
|
||||
if not headlines:
|
||||
return []
|
||||
|
||||
unique = []
|
||||
for article in headlines:
|
||||
title = article.get("title", "")
|
||||
is_dupe = False
|
||||
for existing in unique:
|
||||
if title_similarity(title, existing.get("title", "")) > threshold:
|
||||
is_dupe = True
|
||||
break
|
||||
if not is_dupe:
|
||||
unique.append(article)
|
||||
|
||||
return unique
|
||||
|
||||
|
||||
def classify_category(title: str, description: str = "") -> list[str]:
|
||||
"""Classify headline into categories based on keywords."""
|
||||
text = f"{title} {description}".lower()
|
||||
categories = []
|
||||
|
||||
for category, keywords in CATEGORY_KEYWORDS.items():
|
||||
for keyword in keywords:
|
||||
if keyword in text:
|
||||
categories.append(category)
|
||||
break
|
||||
|
||||
return categories if categories else ["general"]
|
||||
|
||||
|
||||
def score_market_impact(title: str, description: str = "") -> float:
|
||||
"""Score market impact (0-1)."""
|
||||
text = f"{title} {description}".lower()
|
||||
score = 0.3 # Base score
|
||||
|
||||
# High impact indicators
|
||||
high_impact = ["fed", "rate cut", "rate hike", "earnings", "guidance", "sanctions", "war", "oil", "recession"]
|
||||
for term in high_impact:
|
||||
if term in text:
|
||||
score += 0.15
|
||||
|
||||
# Medium impact
|
||||
medium_impact = ["profit", "revenue", "gdp", "inflation", "tariff", "merger", "acquisition"]
|
||||
for term in medium_impact:
|
||||
if term in text:
|
||||
score += 0.1
|
||||
|
||||
return min(score, 1.0)
|
||||
|
||||
|
||||
def score_novelty(article: dict) -> float:
|
||||
"""Score novelty based on recency (0-1)."""
|
||||
published_at = article.get("published_at")
|
||||
if not published_at:
|
||||
return 0.5 # Unknown = medium
|
||||
|
||||
try:
|
||||
if isinstance(published_at, str):
|
||||
pub_time = datetime.fromisoformat(published_at.replace("Z", "+00:00"))
|
||||
else:
|
||||
pub_time = published_at
|
||||
|
||||
hours_old = (datetime.now(pub_time.tzinfo) - pub_time).total_seconds() / 3600
|
||||
|
||||
if hours_old < 2:
|
||||
return 1.0
|
||||
elif hours_old < 6:
|
||||
return 0.8
|
||||
elif hours_old < 12:
|
||||
return 0.6
|
||||
elif hours_old < 24:
|
||||
return 0.4
|
||||
else:
|
||||
return 0.2
|
||||
except Exception:
|
||||
return 0.5
|
||||
|
||||
|
||||
def score_breadth(categories: list[str]) -> float:
|
||||
"""Score breadth - sector-wide vs single-stock (0-1)."""
|
||||
# More categories = broader impact
|
||||
if "macro" in categories or "geopolitics" in categories:
|
||||
return 0.9
|
||||
if "energy" in categories:
|
||||
return 0.7
|
||||
if len(categories) > 1:
|
||||
return 0.6
|
||||
return 0.4
|
||||
|
||||
|
||||
def score_credibility(source: str) -> float:
|
||||
"""Score source credibility (0-1)."""
|
||||
return SOURCE_CREDIBILITY.get(source, 0.5)
|
||||
|
||||
|
||||
def calculate_score(article: dict, weights: dict, category_counts: dict) -> float:
|
||||
"""Calculate overall score for a headline."""
|
||||
title = article.get("title", "")
|
||||
description = article.get("description", "")
|
||||
source = article.get("source", "")
|
||||
categories = classify_category(title, description)
|
||||
article["_categories"] = categories # Store for later use
|
||||
|
||||
# Component scores
|
||||
impact = score_market_impact(title, description)
|
||||
novelty = score_novelty(article)
|
||||
breadth = score_breadth(categories)
|
||||
credibility = score_credibility(source)
|
||||
|
||||
# Diversity bonus - boost underrepresented categories
|
||||
diversity = 0.0
|
||||
for cat in categories:
|
||||
if category_counts.get(cat, 0) < 1:
|
||||
diversity = 0.5
|
||||
break
|
||||
elif category_counts.get(cat, 0) < 2:
|
||||
diversity = 0.3
|
||||
|
||||
# Weighted sum
|
||||
score = (
|
||||
impact * weights.get("market_impact", 0.4) +
|
||||
novelty * weights.get("novelty", 0.2) +
|
||||
breadth * weights.get("breadth", 0.2) +
|
||||
credibility * weights.get("credibility", 0.1) +
|
||||
diversity * weights.get("diversity", 0.1)
|
||||
)
|
||||
|
||||
article["_score"] = round(score, 3)
|
||||
article["_impact"] = round(impact, 3)
|
||||
article["_novelty"] = round(novelty, 3)
|
||||
|
||||
return score
|
||||
|
||||
|
||||
def apply_source_cap(ranked: list[dict], cap: int = 2) -> list[dict]:
|
||||
"""Apply source cap - max N items per outlet."""
|
||||
source_counts = {}
|
||||
result = []
|
||||
|
||||
for article in ranked:
|
||||
source = article.get("source", "Unknown")
|
||||
if source_counts.get(source, 0) < cap:
|
||||
result.append(article)
|
||||
source_counts[source] = source_counts.get(source, 0) + 1
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def ensure_diversity(selected: list[dict], candidates: list[dict], required: list[str]) -> list[dict]:
|
||||
"""Ensure at least one headline from required categories if available."""
|
||||
result = list(selected)
|
||||
covered = set()
|
||||
|
||||
for article in result:
|
||||
for cat in article.get("_categories", []):
|
||||
covered.add(cat)
|
||||
|
||||
for req_cat in required:
|
||||
if req_cat not in covered:
|
||||
# Find candidate from this category
|
||||
for candidate in candidates:
|
||||
if candidate not in result and req_cat in candidate.get("_categories", []):
|
||||
result.append(candidate)
|
||||
covered.add(req_cat)
|
||||
break
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def rank_headlines(headlines: list[dict], config: dict | None = None) -> dict:
|
||||
"""
|
||||
Rank headlines deterministically.
|
||||
|
||||
Args:
|
||||
headlines: List of headline dicts with title, source, description, etc.
|
||||
config: Optional config overrides
|
||||
|
||||
Returns:
|
||||
{"must_read": [...], "scan": [...]}
|
||||
"""
|
||||
cfg = {**DEFAULT_CONFIG, **(config or {})}
|
||||
weights = cfg.get("weights", DEFAULT_CONFIG["weights"])
|
||||
|
||||
if not headlines:
|
||||
return {"must_read": [], "scan": []}
|
||||
|
||||
# Step 1: Deduplicate
|
||||
unique = deduplicate_headlines(headlines, cfg["dedupe_threshold"])
|
||||
|
||||
# Step 2: Score all headlines
|
||||
category_counts = {}
|
||||
for article in unique:
|
||||
calculate_score(article, weights, category_counts)
|
||||
for cat in article.get("_categories", []):
|
||||
category_counts[cat] = category_counts.get(cat, 0) + 1
|
||||
|
||||
# Step 3: Sort by score
|
||||
ranked = sorted(unique, key=lambda x: x.get("_score", 0), reverse=True)
|
||||
|
||||
# Step 4: Apply source cap
|
||||
capped = apply_source_cap(ranked, cfg["source_cap"])
|
||||
|
||||
# Step 5: Select must_read with diversity quota
|
||||
# Leave room for diversity additions by taking count-1 initially
|
||||
must_read_candidates = [a for a in capped if a.get("_score", 0) >= cfg["must_read_min_score"]]
|
||||
must_read_count = cfg["must_read_count"]
|
||||
must_read = must_read_candidates[:max(1, must_read_count - 2)] # Reserve 2 slots for diversity
|
||||
must_read = ensure_diversity(must_read, capped, ["macro", "equities", "geopolitics"])
|
||||
must_read = must_read[:must_read_count] # Final trim to exact count
|
||||
|
||||
# Step 6: Select scan (additional items)
|
||||
scan_candidates = [a for a in capped if a not in must_read and a.get("_score", 0) >= cfg["scan_min_score"]]
|
||||
scan = scan_candidates[:cfg["scan_count"]]
|
||||
|
||||
return {
|
||||
"must_read": must_read,
|
||||
"scan": scan,
|
||||
"total_processed": len(headlines),
|
||||
"after_dedupe": len(unique),
|
||||
}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Test with sample data
|
||||
test_headlines = [
|
||||
{"title": "Fed signals rate cut in March", "source": "WSJ", "description": "Federal Reserve hints at policy shift"},
|
||||
{"title": "Apple earnings beat expectations", "source": "CNBC", "description": "Revenue up 15%"},
|
||||
{"title": "Oil prices surge on OPEC cuts", "source": "Reuters", "description": "Brent crude hits $90"},
|
||||
{"title": "China-US trade tensions escalate", "source": "Bloomberg", "description": "New tariffs announced"},
|
||||
{"title": "Tech stocks rally on AI optimism", "source": "Yahoo Finance", "description": "Nvidia leads gains"},
|
||||
{"title": "Fed hints at rate reduction", "source": "MarketWatch", "description": "Same story as WSJ"}, # Dupe
|
||||
]
|
||||
|
||||
result = rank_headlines(test_headlines)
|
||||
print("MUST_READ:")
|
||||
for h in result["must_read"]:
|
||||
print(f" [{h['_score']:.2f}] {h['title']} ({h['source']})")
|
||||
print("\nSCAN:")
|
||||
for h in result["scan"]:
|
||||
print(f" [{h['_score']:.2f}] {h['title']} ({h['source']})")
|
||||
283
scripts/research.py
Normal file
283
scripts/research.py
Normal file
@@ -0,0 +1,283 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Research Module - Deep research using Gemini CLI.
|
||||
Crawls articles, finds correlations, researches companies.
|
||||
Outputs research_report.md for later analysis.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
from utils import ensure_venv
|
||||
|
||||
from fetch_news import PortfolioError, get_market_news, get_portfolio_news
|
||||
|
||||
SCRIPT_DIR = Path(__file__).parent
|
||||
CONFIG_DIR = SCRIPT_DIR.parent / "config"
|
||||
OUTPUT_DIR = SCRIPT_DIR.parent / "research"
|
||||
|
||||
|
||||
ensure_venv()
|
||||
|
||||
|
||||
def format_market_data(market_data: dict) -> str:
|
||||
"""Format market data for research prompt."""
|
||||
lines = ["## Market Data\n"]
|
||||
|
||||
for region, data in market_data.get('markets', {}).items():
|
||||
lines.append(f"### {data['name']}")
|
||||
for symbol, idx in data.get('indices', {}).items():
|
||||
if 'data' in idx and idx['data']:
|
||||
price = idx['data'].get('price', 'N/A')
|
||||
change_pct = idx['data'].get('change_percent', 0)
|
||||
emoji = '📈' if change_pct >= 0 else '📉'
|
||||
lines.append(f"- {idx['name']}: {price} ({change_pct:+.2f}%) {emoji}")
|
||||
lines.append("")
|
||||
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
def format_headlines(headlines: list) -> str:
|
||||
"""Format headlines for research prompt."""
|
||||
lines = ["## Current Headlines\n"]
|
||||
|
||||
for article in headlines[:20]:
|
||||
source = article.get('source', 'Unknown')
|
||||
title = article.get('title', '')
|
||||
link = article.get('link', '')
|
||||
lines.append(f"- [{source}] {title}")
|
||||
if link:
|
||||
lines.append(f" URL: {link}")
|
||||
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
def format_portfolio_news(portfolio_data: dict) -> str:
|
||||
"""Format portfolio news for research prompt."""
|
||||
lines = ["## Portfolio Analysis\n"]
|
||||
|
||||
for symbol, data in portfolio_data.get('stocks', {}).items():
|
||||
quote = data.get('quote', {})
|
||||
price = quote.get('price', 'N/A')
|
||||
change_pct = quote.get('change_percent', 0)
|
||||
|
||||
lines.append(f"### {symbol} (${price}, {change_pct:+.2f}%)")
|
||||
|
||||
for article in data.get('articles', [])[:5]:
|
||||
title = article.get('title', '')
|
||||
link = article.get('link', '')
|
||||
lines.append(f"- {title}")
|
||||
if link:
|
||||
lines.append(f" URL: {link}")
|
||||
lines.append("")
|
||||
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
def gemini_available() -> bool:
|
||||
return shutil.which('gemini') is not None
|
||||
|
||||
|
||||
def research_with_gemini(content: str, focus_areas: list = None) -> str:
|
||||
"""Perform deep research using Gemini CLI.
|
||||
|
||||
Args:
|
||||
content: Combined market/headlines/portfolio content
|
||||
focus_areas: Optional list of focus areas (e.g., ['earnings', 'macro', 'sectors'])
|
||||
|
||||
Returns:
|
||||
Research report text
|
||||
"""
|
||||
focus_prompt = ""
|
||||
if focus_areas:
|
||||
focus_prompt = f"""
|
||||
Focus areas for the research:
|
||||
{', '.join(f'- {area}' for area in focus_areas)}
|
||||
|
||||
Go deep on each area.
|
||||
"""
|
||||
|
||||
prompt = f"""You are an experienced investment research analyst.
|
||||
|
||||
Your task is to deliver deep research on current market developments.
|
||||
|
||||
{focus_prompt}
|
||||
Please analyze the following market data:
|
||||
|
||||
{content}
|
||||
|
||||
## Analysis Requirements:
|
||||
|
||||
1. **Macro Trends**: What is driving the market today? Which economic data/decisions matter?
|
||||
|
||||
2. **Sector Analysis**: Which sectors are performing best/worst? Why?
|
||||
|
||||
3. **Company News**: Relevant earnings, M&A, product launches?
|
||||
|
||||
4. **Risks**: What downside risks should be noted?
|
||||
|
||||
5. **Opportunities**: Which positive developments offer opportunities?
|
||||
|
||||
6. **Correlations**: Are there links between different news items/asset classes?
|
||||
|
||||
7. **Trade Ideas**: Concrete setups based on the analysis (not financial advice!)
|
||||
|
||||
8. **Sources**: Original links for further research
|
||||
|
||||
Be analytical, objective, and opinionated where appropriate.
|
||||
Deliver a substantial report (500-800 words).
|
||||
"""
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
['gemini', prompt],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=120
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
return result.stdout.strip()
|
||||
else:
|
||||
return f"⚠️ Gemini research error: {result.stderr}"
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
return "⚠️ Gemini research timeout"
|
||||
except FileNotFoundError:
|
||||
return "⚠️ Gemini CLI not found. Install: brew install gemini-cli"
|
||||
|
||||
|
||||
def format_raw_data_report(market_data: dict, portfolio_data: dict) -> str:
|
||||
parts = []
|
||||
if market_data:
|
||||
parts.append(format_market_data(market_data))
|
||||
if market_data.get('headlines'):
|
||||
parts.append(format_headlines(market_data['headlines']))
|
||||
if portfolio_data and 'error' not in portfolio_data:
|
||||
parts.append(format_portfolio_news(portfolio_data))
|
||||
return '\n\n'.join(parts)
|
||||
|
||||
|
||||
def generate_research_content(market_data: dict, portfolio_data: dict, focus_areas: list = None) -> dict:
|
||||
raw_report = format_raw_data_report(market_data, portfolio_data)
|
||||
if not raw_report.strip():
|
||||
return {
|
||||
'report': '',
|
||||
'source': 'none'
|
||||
}
|
||||
if gemini_available():
|
||||
return {
|
||||
'report': research_with_gemini(raw_report, focus_areas),
|
||||
'source': 'gemini'
|
||||
}
|
||||
return {
|
||||
'report': raw_report,
|
||||
'source': 'raw'
|
||||
}
|
||||
|
||||
|
||||
def generate_research_report(args):
|
||||
"""Generate full research report."""
|
||||
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
config_path = CONFIG_DIR / "config.json"
|
||||
if not config_path.exists():
|
||||
print("⚠️ No config found. Run 'finance-news wizard' first.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Fetch fresh data
|
||||
print("📡 Fetching market data...", file=sys.stderr)
|
||||
|
||||
# Get market overview
|
||||
market_data = get_market_news(
|
||||
args.limit if hasattr(args, 'limit') else 5,
|
||||
regions=args.regions.split(',') if hasattr(args, 'regions') else ["us", "europe"],
|
||||
max_indices_per_region=2
|
||||
)
|
||||
|
||||
# Get portfolio news
|
||||
try:
|
||||
portfolio_data = get_portfolio_news(
|
||||
args.limit if hasattr(args, 'limit') else 5,
|
||||
args.max_stocks if hasattr(args, 'max_stocks') else 10
|
||||
)
|
||||
except PortfolioError as exc:
|
||||
print(f"⚠️ Skipping portfolio: {exc}", file=sys.stderr)
|
||||
portfolio_data = None
|
||||
|
||||
# Build report
|
||||
focus_areas = None
|
||||
if hasattr(args, 'focus') and args.focus:
|
||||
focus_areas = args.focus.split(',')
|
||||
|
||||
research_result = generate_research_content(market_data, portfolio_data, focus_areas)
|
||||
research_report = research_result['report']
|
||||
source = research_result['source']
|
||||
|
||||
if not research_report.strip():
|
||||
print("⚠️ No data available for research", file=sys.stderr)
|
||||
return
|
||||
|
||||
if source == 'gemini':
|
||||
print("🔬 Running deep research with Gemini...", file=sys.stderr)
|
||||
else:
|
||||
print("🧾 Gemini not available; using raw data report", file=sys.stderr)
|
||||
|
||||
# Add metadata header
|
||||
timestamp = datetime.now().isoformat()
|
||||
date_str = datetime.now().strftime("%Y-%m-%d %H:%M")
|
||||
|
||||
full_report = f"""# Market Research Report
|
||||
**Generiert:** {date_str}
|
||||
**Quelle:** Finance News Skill
|
||||
|
||||
---
|
||||
|
||||
{research_report}
|
||||
|
||||
---
|
||||
|
||||
*This report was generated automatically. Not financial advice.*
|
||||
"""
|
||||
|
||||
# Save to file
|
||||
output_file = OUTPUT_DIR / f"research_{datetime.now().strftime('%Y-%m-%d')}.md"
|
||||
with open(output_file, 'w') as f:
|
||||
f.write(full_report)
|
||||
|
||||
print(f"✅ Research report saved to: {output_file}", file=sys.stderr)
|
||||
|
||||
# Also output to stdout
|
||||
if args.json:
|
||||
print(json.dumps({
|
||||
'report': research_report,
|
||||
'saved_to': str(output_file),
|
||||
'timestamp': timestamp
|
||||
}))
|
||||
else:
|
||||
print("\n" + "="*60)
|
||||
print("RESEARCH REPORT")
|
||||
print("="*60)
|
||||
print(research_report)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Deep Market Research')
|
||||
parser.add_argument('--limit', type=int, default=5, help='Max headlines per source')
|
||||
parser.add_argument('--regions', default='us,europe', help='Comma-separated regions')
|
||||
parser.add_argument('--max-stocks', type=int, default=10, help='Max portfolio stocks')
|
||||
parser.add_argument('--focus', help='Focus areas (comma-separated)')
|
||||
parser.add_argument('--json', action='store_true', help='Output as JSON')
|
||||
|
||||
args = parser.parse_args()
|
||||
generate_research_report(args)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
290
scripts/setup.py
Normal file
290
scripts/setup.py
Normal file
@@ -0,0 +1,290 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Finance News Skill - Interactive Setup
|
||||
Configures RSS feeds, WhatsApp channels, and cron jobs.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
SCRIPT_DIR = Path(__file__).parent
|
||||
CONFIG_DIR = SCRIPT_DIR.parent / "config"
|
||||
SOURCES_FILE = CONFIG_DIR / "config.json"
|
||||
|
||||
|
||||
def load_sources():
|
||||
"""Load current sources configuration."""
|
||||
if SOURCES_FILE.exists():
|
||||
with open(SOURCES_FILE, 'r') as f:
|
||||
return json.load(f)
|
||||
return get_default_sources()
|
||||
|
||||
|
||||
def save_sources(sources: dict):
|
||||
"""Save sources configuration."""
|
||||
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
||||
with open(SOURCES_FILE, 'w') as f:
|
||||
json.dump(sources, f, indent=2)
|
||||
print(f"✅ Configuration saved to {SOURCES_FILE}")
|
||||
|
||||
|
||||
def get_default_sources():
|
||||
"""Return default source configuration."""
|
||||
config_path = CONFIG_DIR / "config.json"
|
||||
if config_path.exists():
|
||||
with open(config_path, 'r') as f:
|
||||
return json.load(f)
|
||||
return {}
|
||||
|
||||
|
||||
def prompt(message: str, default: str = "") -> str:
|
||||
"""Prompt for input with optional default."""
|
||||
if default:
|
||||
result = input(f"{message} [{default}]: ").strip()
|
||||
return result if result else default
|
||||
return input(f"{message}: ").strip()
|
||||
|
||||
|
||||
def prompt_bool(message: str, default: bool = True) -> bool:
|
||||
"""Prompt for yes/no input."""
|
||||
default_str = "Y/n" if default else "y/N"
|
||||
result = input(f"{message} [{default_str}]: ").strip().lower()
|
||||
if not result:
|
||||
return default
|
||||
return result in ('y', 'yes', '1', 'true')
|
||||
|
||||
|
||||
def setup_rss_feeds(sources: dict):
|
||||
"""Interactive RSS feed configuration."""
|
||||
print("\n📰 RSS Feed Configuration\n")
|
||||
print("Enable/disable news sources:\n")
|
||||
|
||||
for feed_id, feed_config in sources['rss_feeds'].items():
|
||||
name = feed_config.get('name', feed_id)
|
||||
current = feed_config.get('enabled', True)
|
||||
enabled = prompt_bool(f" {name}", current)
|
||||
sources['rss_feeds'][feed_id]['enabled'] = enabled
|
||||
|
||||
print("\n Add custom RSS feed? (leave blank to skip)")
|
||||
custom_name = prompt(" Feed name", "")
|
||||
if custom_name:
|
||||
custom_url = prompt(" Feed URL")
|
||||
sources['rss_feeds'][custom_name.lower().replace(' ', '_')] = {
|
||||
"name": custom_name,
|
||||
"enabled": True,
|
||||
"main": custom_url
|
||||
}
|
||||
print(f" ✅ Added {custom_name}")
|
||||
|
||||
|
||||
def setup_markets(sources: dict):
|
||||
"""Interactive market configuration."""
|
||||
print("\n📊 Market Coverage\n")
|
||||
print("Enable/disable market regions:\n")
|
||||
|
||||
for market_id, market_config in sources['markets'].items():
|
||||
name = market_config.get('name', market_id)
|
||||
current = market_config.get('enabled', True)
|
||||
enabled = prompt_bool(f" {name}", current)
|
||||
sources['markets'][market_id]['enabled'] = enabled
|
||||
|
||||
|
||||
def setup_delivery(sources: dict):
|
||||
"""Interactive delivery channel configuration."""
|
||||
print("\n📤 Delivery Channels\n")
|
||||
|
||||
# Ensure delivery dict exists
|
||||
if 'delivery' not in sources:
|
||||
sources['delivery'] = {
|
||||
'whatsapp': {'enabled': True, 'group': ''},
|
||||
'telegram': {'enabled': False, 'group': ''}
|
||||
}
|
||||
|
||||
# WhatsApp
|
||||
wa_enabled = prompt_bool("Enable WhatsApp delivery",
|
||||
sources.get('delivery', {}).get('whatsapp', {}).get('enabled', True))
|
||||
sources['delivery']['whatsapp']['enabled'] = wa_enabled
|
||||
|
||||
if wa_enabled:
|
||||
wa_group = prompt(" WhatsApp group name or JID",
|
||||
sources['delivery']['whatsapp'].get('group', ''))
|
||||
sources['delivery']['whatsapp']['group'] = wa_group
|
||||
|
||||
# Telegram
|
||||
tg_enabled = prompt_bool("Enable Telegram delivery",
|
||||
sources['delivery']['telegram'].get('enabled', False))
|
||||
sources['delivery']['telegram']['enabled'] = tg_enabled
|
||||
|
||||
if tg_enabled:
|
||||
tg_group = prompt(" Telegram group name or ID",
|
||||
sources['delivery']['telegram'].get('group', ''))
|
||||
sources['delivery']['telegram']['group'] = tg_group
|
||||
|
||||
|
||||
def setup_language(sources: dict):
|
||||
"""Interactive language configuration."""
|
||||
print("\n🌐 Language Settings\n")
|
||||
|
||||
current_lang = sources['language'].get('default', 'de')
|
||||
lang = prompt("Default language (de/en)", current_lang)
|
||||
if lang in sources['language']['supported']:
|
||||
sources['language']['default'] = lang
|
||||
else:
|
||||
print(f" ⚠️ Unsupported language '{lang}', keeping '{current_lang}'")
|
||||
|
||||
|
||||
def setup_schedule(sources: dict):
|
||||
"""Interactive schedule configuration."""
|
||||
print("\n⏰ Briefing Schedule\n")
|
||||
|
||||
# Morning
|
||||
morning = sources['schedule']['morning']
|
||||
morning_enabled = prompt_bool(f"Enable morning briefing ({morning['description']})",
|
||||
morning.get('enabled', True))
|
||||
sources['schedule']['morning']['enabled'] = morning_enabled
|
||||
|
||||
if morning_enabled:
|
||||
morning_cron = prompt(" Morning cron expression", morning.get('cron', '30 6 * * 1-5'))
|
||||
sources['schedule']['morning']['cron'] = morning_cron
|
||||
|
||||
# Evening
|
||||
evening = sources['schedule']['evening']
|
||||
evening_enabled = prompt_bool(f"Enable evening briefing ({evening['description']})",
|
||||
evening.get('enabled', True))
|
||||
sources['schedule']['evening']['enabled'] = evening_enabled
|
||||
|
||||
if evening_enabled:
|
||||
evening_cron = prompt(" Evening cron expression", evening.get('cron', '0 13 * * 1-5'))
|
||||
sources['schedule']['evening']['cron'] = evening_cron
|
||||
|
||||
# Timezone
|
||||
tz = prompt("Timezone", sources['schedule']['morning'].get('timezone', 'America/Los_Angeles'))
|
||||
sources['schedule']['morning']['timezone'] = tz
|
||||
sources['schedule']['evening']['timezone'] = tz
|
||||
|
||||
|
||||
def setup_cron_jobs(sources: dict):
|
||||
"""Set up OpenClaw cron jobs based on configuration."""
|
||||
print("\n📅 Setting up cron jobs...\n")
|
||||
|
||||
schedule = sources.get('schedule', {})
|
||||
delivery = sources.get('delivery', {})
|
||||
language = sources.get('language', {}).get('default', 'de')
|
||||
|
||||
# Determine delivery target
|
||||
if delivery.get('whatsapp', {}).get('enabled'):
|
||||
group = delivery['whatsapp'].get('group', '')
|
||||
send_cmd = f"--send --group '{group}'" if group else ""
|
||||
elif delivery.get('telegram', {}).get('enabled'):
|
||||
group = delivery['telegram'].get('group', '')
|
||||
send_cmd = f"--send --group '{group}'" # Would need telegram support
|
||||
else:
|
||||
send_cmd = ""
|
||||
|
||||
# Morning job
|
||||
if schedule.get('morning', {}).get('enabled'):
|
||||
morning_cron = schedule['morning'].get('cron', '30 6 * * 1-5')
|
||||
tz = schedule['morning'].get('timezone', 'America/Los_Angeles')
|
||||
|
||||
print(f" Creating morning briefing job: {morning_cron} ({tz})")
|
||||
# Note: Actual cron creation would happen via openclaw cron add
|
||||
print(f" ✅ Morning briefing configured")
|
||||
|
||||
# Evening job
|
||||
if schedule.get('evening', {}).get('enabled'):
|
||||
evening_cron = schedule['evening'].get('cron', '0 13 * * 1-5')
|
||||
tz = schedule['evening'].get('timezone', 'America/Los_Angeles')
|
||||
|
||||
print(f" Creating evening briefing job: {evening_cron} ({tz})")
|
||||
print(f" ✅ Evening briefing configured")
|
||||
|
||||
|
||||
def run_setup(args):
|
||||
"""Run interactive setup wizard."""
|
||||
print("\n" + "="*60)
|
||||
print("📈 Finance News Skill - Setup Wizard")
|
||||
print("="*60)
|
||||
|
||||
# Load existing or default config
|
||||
if args.reset:
|
||||
sources = get_default_sources()
|
||||
print("\n⚠️ Starting with fresh configuration")
|
||||
else:
|
||||
sources = load_sources()
|
||||
if SOURCES_FILE.exists():
|
||||
print(f"\n📂 Loaded existing configuration from {SOURCES_FILE}")
|
||||
else:
|
||||
print("\n📂 No existing configuration found, using defaults")
|
||||
|
||||
# Run through each section
|
||||
if not args.section or args.section == 'feeds':
|
||||
setup_rss_feeds(sources)
|
||||
|
||||
if not args.section or args.section == 'markets':
|
||||
setup_markets(sources)
|
||||
|
||||
if not args.section or args.section == 'delivery':
|
||||
setup_delivery(sources)
|
||||
|
||||
if not args.section or args.section == 'language':
|
||||
setup_language(sources)
|
||||
|
||||
if not args.section or args.section == 'schedule':
|
||||
setup_schedule(sources)
|
||||
|
||||
# Save configuration
|
||||
print("\n" + "-"*60)
|
||||
if prompt_bool("Save configuration?", True):
|
||||
save_sources(sources)
|
||||
|
||||
# Set up cron jobs
|
||||
if prompt_bool("Set up cron jobs now?", True):
|
||||
setup_cron_jobs(sources)
|
||||
else:
|
||||
print("❌ Configuration not saved")
|
||||
|
||||
print("\n✅ Setup complete!")
|
||||
print("\nNext steps:")
|
||||
print(" • Run 'finance-news portfolio-list' to check your watchlist")
|
||||
print(" • Run 'finance-news briefing --morning' to test a briefing")
|
||||
print(" • Run 'finance-news market' to see market overview")
|
||||
print()
|
||||
|
||||
|
||||
def show_config(args):
|
||||
"""Show current configuration."""
|
||||
sources = load_sources()
|
||||
print(json.dumps(sources, indent=2))
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Finance News Setup')
|
||||
subparsers = parser.add_subparsers(dest='command')
|
||||
|
||||
# Setup command (default)
|
||||
setup_parser = subparsers.add_parser('wizard', help='Run setup wizard')
|
||||
setup_parser.add_argument('--reset', action='store_true', help='Reset to defaults')
|
||||
setup_parser.add_argument('--section', choices=['feeds', 'markets', 'delivery', 'language', 'schedule'],
|
||||
help='Configure specific section only')
|
||||
setup_parser.set_defaults(func=run_setup)
|
||||
|
||||
# Show config
|
||||
show_parser = subparsers.add_parser('show', help='Show current configuration')
|
||||
show_parser.set_defaults(func=show_config)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.command:
|
||||
args.func(args)
|
||||
else:
|
||||
# Default to wizard
|
||||
args.reset = False
|
||||
args.section = None
|
||||
run_setup(args)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
335
scripts/stocks.py
Normal file
335
scripts/stocks.py
Normal file
@@ -0,0 +1,335 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
stocks.py - Unified stock management for holdings and watchlist.
|
||||
|
||||
Single source of truth for:
|
||||
- Holdings (stocks you own)
|
||||
- Watchlist (stocks you're watching to buy)
|
||||
|
||||
Usage:
|
||||
from stocks import load_stocks, save_stocks, get_holdings, get_watchlist
|
||||
from stocks import add_to_watchlist, add_to_holdings, move_to_holdings
|
||||
|
||||
CLI:
|
||||
stocks.py list [--holdings|--watchlist]
|
||||
stocks.py add-watchlist TICKER [--target 380] [--notes "Buy zone"]
|
||||
stocks.py add-holding TICKER --name "Company" [--category "Tech"]
|
||||
stocks.py move TICKER # watchlist → holdings (you bought it)
|
||||
stocks.py remove TICKER [--from holdings|watchlist]
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
# Default path - can be overridden
|
||||
STOCKS_FILE = Path(__file__).parent.parent / "config" / "stocks.json"
|
||||
|
||||
|
||||
def load_stocks(path: Optional[Path] = None) -> dict:
|
||||
"""Load the unified stocks file."""
|
||||
path = path or STOCKS_FILE
|
||||
if not path.exists():
|
||||
return {
|
||||
"version": "1.0",
|
||||
"updated": datetime.now().strftime("%Y-%m-%d"),
|
||||
"holdings": [],
|
||||
"watchlist": [],
|
||||
"alert_definitions": {}
|
||||
}
|
||||
|
||||
with open(path, 'r') as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def save_stocks(data: dict, path: Optional[Path] = None):
|
||||
"""Save the unified stocks file."""
|
||||
path = path or STOCKS_FILE
|
||||
data["updated"] = datetime.now().strftime("%Y-%m-%d")
|
||||
|
||||
with open(path, 'w') as f:
|
||||
json.dump(data, f, indent=2)
|
||||
|
||||
|
||||
def get_holdings(data: Optional[dict] = None) -> list:
|
||||
"""Get list of holdings."""
|
||||
if data is None:
|
||||
data = load_stocks()
|
||||
return data.get("holdings", [])
|
||||
|
||||
|
||||
def get_watchlist(data: Optional[dict] = None) -> list:
|
||||
"""Get list of watchlist items."""
|
||||
if data is None:
|
||||
data = load_stocks()
|
||||
return data.get("watchlist", [])
|
||||
|
||||
|
||||
def get_holding_tickers(data: Optional[dict] = None) -> set:
|
||||
"""Get set of holding tickers for quick lookup."""
|
||||
holdings = get_holdings(data)
|
||||
return {h.get("ticker") for h in holdings}
|
||||
|
||||
|
||||
def get_watchlist_tickers(data: Optional[dict] = None) -> set:
|
||||
"""Get set of watchlist tickers for quick lookup."""
|
||||
watchlist = get_watchlist(data)
|
||||
return {w.get("ticker") for w in watchlist}
|
||||
|
||||
|
||||
def add_to_watchlist(
|
||||
ticker: str,
|
||||
target: Optional[float] = None,
|
||||
stop: Optional[float] = None,
|
||||
notes: str = "",
|
||||
alerts: Optional[list] = None
|
||||
) -> bool:
|
||||
"""Add a stock to the watchlist."""
|
||||
data = load_stocks()
|
||||
|
||||
# Check if already in watchlist
|
||||
for w in data["watchlist"]:
|
||||
if w.get("ticker") == ticker:
|
||||
# Update existing
|
||||
if target is not None:
|
||||
w["target"] = target
|
||||
if stop is not None:
|
||||
w["stop"] = stop
|
||||
if notes:
|
||||
w["notes"] = notes
|
||||
if alerts is not None:
|
||||
w["alerts"] = alerts
|
||||
save_stocks(data)
|
||||
return True
|
||||
|
||||
# Add new
|
||||
data["watchlist"].append({
|
||||
"ticker": ticker,
|
||||
"target": target,
|
||||
"stop": stop,
|
||||
"alerts": alerts or [],
|
||||
"notes": notes
|
||||
})
|
||||
data["watchlist"].sort(key=lambda x: x.get("ticker", ""))
|
||||
save_stocks(data)
|
||||
return True
|
||||
|
||||
|
||||
def add_to_holdings(
|
||||
ticker: str,
|
||||
name: str = "",
|
||||
category: str = "",
|
||||
notes: str = "",
|
||||
target: Optional[float] = None,
|
||||
stop: Optional[float] = None,
|
||||
alerts: Optional[list] = None
|
||||
) -> bool:
|
||||
"""Add a stock to holdings. Target/stop for 'buy more' alerts."""
|
||||
data = load_stocks()
|
||||
|
||||
# Check if already in holdings
|
||||
for h in data["holdings"]:
|
||||
if h.get("ticker") == ticker:
|
||||
# Update existing
|
||||
if name:
|
||||
h["name"] = name
|
||||
if category:
|
||||
h["category"] = category
|
||||
if notes:
|
||||
h["notes"] = notes
|
||||
if target is not None:
|
||||
h["target"] = target
|
||||
if stop is not None:
|
||||
h["stop"] = stop
|
||||
if alerts is not None:
|
||||
h["alerts"] = alerts
|
||||
save_stocks(data)
|
||||
return True
|
||||
|
||||
# Add new
|
||||
data["holdings"].append({
|
||||
"ticker": ticker,
|
||||
"name": name,
|
||||
"category": category,
|
||||
"notes": notes,
|
||||
"target": target,
|
||||
"stop": stop,
|
||||
"alerts": alerts or []
|
||||
})
|
||||
data["holdings"].sort(key=lambda x: x.get("ticker", ""))
|
||||
save_stocks(data)
|
||||
return True
|
||||
|
||||
|
||||
def move_to_holdings(
|
||||
ticker: str,
|
||||
name: str = "",
|
||||
category: str = "",
|
||||
notes: str = ""
|
||||
) -> bool:
|
||||
"""Move a stock from watchlist to holdings (you bought it)."""
|
||||
data = load_stocks()
|
||||
|
||||
# Find in watchlist
|
||||
watchlist_item = None
|
||||
for i, w in enumerate(data["watchlist"]):
|
||||
if w.get("ticker") == ticker:
|
||||
watchlist_item = data["watchlist"].pop(i)
|
||||
break
|
||||
|
||||
if not watchlist_item:
|
||||
print(f"⚠️ {ticker} not found in watchlist", file=sys.stderr)
|
||||
return False
|
||||
|
||||
# Add to holdings
|
||||
data["holdings"].append({
|
||||
"ticker": ticker,
|
||||
"name": name or watchlist_item.get("notes", ""),
|
||||
"category": category,
|
||||
"notes": notes or f"Bought (was on watchlist with target ${watchlist_item.get('target', 'N/A')})"
|
||||
})
|
||||
data["holdings"].sort(key=lambda x: x.get("ticker", ""))
|
||||
save_stocks(data)
|
||||
return True
|
||||
|
||||
|
||||
def remove_stock(ticker: str, from_list: str = "both") -> bool:
|
||||
"""Remove a stock from holdings, watchlist, or both."""
|
||||
data = load_stocks()
|
||||
removed = False
|
||||
|
||||
if from_list in ("holdings", "both"):
|
||||
original_len = len(data["holdings"])
|
||||
data["holdings"] = [h for h in data["holdings"] if h.get("ticker") != ticker]
|
||||
if len(data["holdings"]) < original_len:
|
||||
removed = True
|
||||
|
||||
if from_list in ("watchlist", "both"):
|
||||
original_len = len(data["watchlist"])
|
||||
data["watchlist"] = [w for w in data["watchlist"] if w.get("ticker") != ticker]
|
||||
if len(data["watchlist"]) < original_len:
|
||||
removed = True
|
||||
|
||||
if removed:
|
||||
save_stocks(data)
|
||||
return removed
|
||||
|
||||
|
||||
def list_stocks(show_holdings: bool = True, show_watchlist: bool = True):
|
||||
"""Print stocks list."""
|
||||
data = load_stocks()
|
||||
|
||||
if show_holdings:
|
||||
print(f"\n📊 HOLDINGS ({len(data['holdings'])})")
|
||||
print("-" * 50)
|
||||
for h in data["holdings"][:20]:
|
||||
print(f" {h['ticker']:10} {h.get('name', '')[:30]}")
|
||||
if len(data["holdings"]) > 20:
|
||||
print(f" ... and {len(data['holdings']) - 20} more")
|
||||
|
||||
if show_watchlist:
|
||||
print(f"\n👀 WATCHLIST ({len(data['watchlist'])})")
|
||||
print("-" * 50)
|
||||
for w in data["watchlist"][:20]:
|
||||
target = f"${w['target']}" if w.get('target') else "no target"
|
||||
print(f" {w['ticker']:10} {target:>10} {w.get('notes', '')[:25]}")
|
||||
if len(data["watchlist"]) > 20:
|
||||
print(f" ... and {len(data['watchlist']) - 20} more")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Unified stock management")
|
||||
subparsers = parser.add_subparsers(dest="command", help="Commands")
|
||||
|
||||
# list
|
||||
list_parser = subparsers.add_parser("list", help="List stocks")
|
||||
list_parser.add_argument("--holdings", action="store_true", help="Show only holdings")
|
||||
list_parser.add_argument("--watchlist", action="store_true", help="Show only watchlist")
|
||||
|
||||
# add-watchlist
|
||||
add_watch = subparsers.add_parser("add-watchlist", help="Add to watchlist")
|
||||
add_watch.add_argument("ticker", help="Stock ticker")
|
||||
add_watch.add_argument("--target", type=float, help="Target price")
|
||||
add_watch.add_argument("--stop", type=float, help="Stop loss")
|
||||
add_watch.add_argument("--notes", default="", help="Notes")
|
||||
|
||||
# add-holding
|
||||
add_hold = subparsers.add_parser("add-holding", help="Add to holdings")
|
||||
add_hold.add_argument("ticker", help="Stock ticker")
|
||||
add_hold.add_argument("--name", default="", help="Company name")
|
||||
add_hold.add_argument("--category", default="", help="Category")
|
||||
add_hold.add_argument("--notes", default="", help="Notes")
|
||||
add_hold.add_argument("--target", type=float, help="Buy-more target price")
|
||||
add_hold.add_argument("--stop", type=float, help="Stop loss price")
|
||||
|
||||
# move (watchlist → holdings)
|
||||
move = subparsers.add_parser("move", help="Move from watchlist to holdings")
|
||||
move.add_argument("ticker", help="Stock ticker")
|
||||
move.add_argument("--name", default="", help="Company name")
|
||||
move.add_argument("--category", default="", help="Category")
|
||||
|
||||
# remove
|
||||
remove = subparsers.add_parser("remove", help="Remove stock")
|
||||
remove.add_argument("ticker", help="Stock ticker")
|
||||
remove.add_argument("--from", dest="from_list", choices=["holdings", "watchlist", "both"],
|
||||
default="both", help="Remove from which list")
|
||||
|
||||
# set-alert (for existing holdings)
|
||||
set_alert = subparsers.add_parser("set-alert", help="Set buy-more/stop alert on holding")
|
||||
set_alert.add_argument("ticker", help="Stock ticker")
|
||||
set_alert.add_argument("--target", type=float, help="Buy-more target price")
|
||||
set_alert.add_argument("--stop", type=float, help="Stop loss price")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.command == "list":
|
||||
show_h = not args.watchlist or args.holdings
|
||||
show_w = not args.holdings or args.watchlist
|
||||
if not args.holdings and not args.watchlist:
|
||||
show_h = show_w = True
|
||||
list_stocks(show_holdings=show_h, show_watchlist=show_w)
|
||||
|
||||
elif args.command == "add-watchlist":
|
||||
add_to_watchlist(args.ticker.upper(), args.target, args.stop, args.notes)
|
||||
print(f"✅ Added {args.ticker.upper()} to watchlist")
|
||||
|
||||
elif args.command == "add-holding":
|
||||
add_to_holdings(args.ticker.upper(), args.name, args.category, args.notes,
|
||||
args.target, args.stop)
|
||||
print(f"✅ Added {args.ticker.upper()} to holdings")
|
||||
|
||||
elif args.command == "move":
|
||||
if move_to_holdings(args.ticker.upper(), args.name, args.category):
|
||||
print(f"✅ Moved {args.ticker.upper()} from watchlist to holdings")
|
||||
|
||||
elif args.command == "remove":
|
||||
if remove_stock(args.ticker.upper(), args.from_list):
|
||||
print(f"✅ Removed {args.ticker.upper()}")
|
||||
else:
|
||||
print(f"⚠️ {args.ticker.upper()} not found")
|
||||
|
||||
elif args.command == "set-alert":
|
||||
data = load_stocks()
|
||||
found = False
|
||||
for h in data["holdings"]:
|
||||
if h.get("ticker") == args.ticker.upper():
|
||||
if args.target is not None:
|
||||
h["target"] = args.target
|
||||
if args.stop is not None:
|
||||
h["stop"] = args.stop
|
||||
save_stocks(data)
|
||||
found = True
|
||||
print(f"✅ Set alert on {args.ticker.upper()}: target=${args.target}, stop=${args.stop}")
|
||||
break
|
||||
if not found:
|
||||
print(f"⚠️ {args.ticker.upper()} not found in holdings")
|
||||
|
||||
else:
|
||||
parser.print_help()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
1728
scripts/summarize.py
Normal file
1728
scripts/summarize.py
Normal file
File diff suppressed because it is too large
Load Diff
158
scripts/translate_portfolio.py
Normal file
158
scripts/translate_portfolio.py
Normal file
@@ -0,0 +1,158 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Translate portfolio headlines in briefing JSON using openclaw.
|
||||
|
||||
Usage: python3 translate_portfolio.py /path/to/briefing.json [--lang de]
|
||||
|
||||
Reads briefing JSON, translates portfolio article headlines via openclaw,
|
||||
writes back the modified JSON.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
def extract_headlines(portfolio_message: str) -> list[str]:
|
||||
"""Extract article headlines (lines starting with •) from portfolio message."""
|
||||
headlines = []
|
||||
for line in portfolio_message.split('\n'):
|
||||
line = line.strip()
|
||||
if line.startswith('•'):
|
||||
# Remove bullet, reference number, and clean up
|
||||
# Format: "• Headline text [1]"
|
||||
match = re.match(r'•\s*(.+?)\s*\[\d+\]$', line)
|
||||
if match:
|
||||
headlines.append(match.group(1))
|
||||
else:
|
||||
# No reference number
|
||||
headlines.append(line[1:].strip())
|
||||
return headlines
|
||||
|
||||
|
||||
def translate_headlines(headlines: list[str], lang: str = "de") -> list[str]:
|
||||
"""Translate headlines using openclaw agent."""
|
||||
if not headlines:
|
||||
return []
|
||||
|
||||
prompt = f"""Translate these English headlines to German.
|
||||
Return ONLY a JSON array of strings in the same order.
|
||||
Example: ["Übersetzung 1", "Übersetzung 2"]
|
||||
Do not add commentary.
|
||||
|
||||
Headlines:
|
||||
"""
|
||||
for idx, title in enumerate(headlines, start=1):
|
||||
prompt += f"{idx}. {title}\n"
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[
|
||||
'openclaw', 'agent',
|
||||
'--session-id', 'finance-news-translate-portfolio',
|
||||
'--message', prompt,
|
||||
'--json',
|
||||
'--timeout', '60'
|
||||
],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=90
|
||||
)
|
||||
except (subprocess.TimeoutExpired, FileNotFoundError, OSError) as e:
|
||||
print(f"⚠️ Translation failed: {e}", file=sys.stderr)
|
||||
return headlines
|
||||
|
||||
if result.returncode != 0:
|
||||
print(f"⚠️ openclaw error: {result.stderr}", file=sys.stderr)
|
||||
return headlines
|
||||
|
||||
# Extract reply from openclaw JSON output
|
||||
# Format: {"result": {"payloads": [{"text": "..."}]}}
|
||||
# Note: openclaw may print plugin loading messages before JSON, so find the JSON start
|
||||
stdout = result.stdout
|
||||
json_start = stdout.find('{')
|
||||
if json_start > 0:
|
||||
stdout = stdout[json_start:]
|
||||
|
||||
try:
|
||||
output = json.loads(stdout)
|
||||
payloads = output.get('result', {}).get('payloads', [])
|
||||
if payloads and payloads[0].get('text'):
|
||||
reply = payloads[0]['text']
|
||||
else:
|
||||
reply = output.get('reply', '') or output.get('message', '') or stdout
|
||||
except json.JSONDecodeError:
|
||||
reply = stdout
|
||||
|
||||
# Parse JSON array from reply
|
||||
json_text = reply.strip()
|
||||
if "```" in json_text:
|
||||
match = re.search(r'```(?:json)?\s*(.*?)```', json_text, re.DOTALL)
|
||||
if match:
|
||||
json_text = match.group(1).strip()
|
||||
|
||||
try:
|
||||
translated = json.loads(json_text)
|
||||
if isinstance(translated, list) and len(translated) == len(headlines):
|
||||
print(f"✅ Translated {len(headlines)} portfolio headlines", file=sys.stderr)
|
||||
return translated
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"⚠️ JSON parse error: {e}", file=sys.stderr)
|
||||
|
||||
print(f"⚠️ Translation failed, using original headlines", file=sys.stderr)
|
||||
return headlines
|
||||
|
||||
|
||||
def replace_headlines(portfolio_message: str, original: list[str], translated: list[str]) -> str:
|
||||
"""Replace original headlines with translated ones in portfolio message."""
|
||||
result = portfolio_message
|
||||
for orig, trans in zip(original, translated):
|
||||
if orig != trans:
|
||||
# Replace the headline text, preserving bullet and reference
|
||||
result = result.replace(f"• {orig}", f"• {trans}")
|
||||
return result
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Translate portfolio headlines')
|
||||
parser.add_argument('json_file', help='Path to briefing JSON file')
|
||||
parser.add_argument('--lang', default='de', help='Target language (default: de)')
|
||||
args = parser.parse_args()
|
||||
|
||||
# Read JSON
|
||||
try:
|
||||
with open(args.json_file, 'r') as f:
|
||||
data = json.load(f)
|
||||
except (FileNotFoundError, json.JSONDecodeError) as e:
|
||||
print(f"❌ Error reading {args.json_file}: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
portfolio_message = data.get('portfolio_message', '')
|
||||
if not portfolio_message:
|
||||
print("No portfolio_message to translate", file=sys.stderr)
|
||||
print(json.dumps(data, ensure_ascii=False, indent=2))
|
||||
return
|
||||
|
||||
# Extract, translate, replace
|
||||
headlines = extract_headlines(portfolio_message)
|
||||
if not headlines:
|
||||
print("No headlines found in portfolio_message", file=sys.stderr)
|
||||
print(json.dumps(data, ensure_ascii=False, indent=2))
|
||||
return
|
||||
|
||||
print(f"📝 Found {len(headlines)} headlines to translate", file=sys.stderr)
|
||||
translated = translate_headlines(headlines, args.lang)
|
||||
|
||||
# Update portfolio message
|
||||
data['portfolio_message'] = replace_headlines(portfolio_message, headlines, translated)
|
||||
|
||||
# Write back
|
||||
with open(args.json_file, 'w') as f:
|
||||
json.dump(data, f, ensure_ascii=False, indent=2)
|
||||
|
||||
print(f"✅ Updated {args.json_file}", file=sys.stderr)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
45
scripts/utils.py
Normal file
45
scripts/utils.py
Normal file
@@ -0,0 +1,45 @@
|
||||
"""Shared helpers."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def ensure_venv() -> None:
|
||||
"""Re-exec inside local venv if available and not already active."""
|
||||
if os.environ.get("FINANCE_NEWS_VENV_BOOTSTRAPPED") == "1":
|
||||
return
|
||||
if sys.prefix != sys.base_prefix:
|
||||
return
|
||||
venv_python = Path(__file__).resolve().parent.parent / "venv" / "bin" / "python3"
|
||||
if not venv_python.exists():
|
||||
print("⚠️ finance-news venv missing; run scripts from the repo venv to avoid dependency errors.", file=sys.stderr)
|
||||
return
|
||||
env = os.environ.copy()
|
||||
env["FINANCE_NEWS_VENV_BOOTSTRAPPED"] = "1"
|
||||
os.execvpe(str(venv_python), [str(venv_python)] + sys.argv, env)
|
||||
|
||||
|
||||
def compute_deadline(deadline_sec: int | None) -> float | None:
|
||||
if deadline_sec is None:
|
||||
return None
|
||||
if deadline_sec <= 0:
|
||||
return None
|
||||
return time.monotonic() + deadline_sec
|
||||
|
||||
|
||||
def time_left(deadline: float | None) -> int | None:
|
||||
if deadline is None:
|
||||
return None
|
||||
remaining = int(deadline - time.monotonic())
|
||||
return remaining
|
||||
|
||||
|
||||
def clamp_timeout(default_timeout: int, deadline: float | None, minimum: int = 1) -> int:
|
||||
remaining = time_left(deadline)
|
||||
if remaining is None:
|
||||
return default_timeout
|
||||
if remaining <= 0:
|
||||
raise TimeoutError("Deadline exceeded")
|
||||
return max(min(default_timeout, remaining), minimum)
|
||||
109
scripts/venv-setup.sh
Normal file
109
scripts/venv-setup.sh
Normal file
@@ -0,0 +1,109 @@
|
||||
#!/usr/bin/env bash
|
||||
# Finance News - venv Setup Script
|
||||
# Creates or rebuilds the Python virtual environment
|
||||
# Handles NixOS libstdc++ issues automatically
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
BASE_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
VENV_DIR="${BASE_DIR}/venv"
|
||||
|
||||
echo "📦 Finance News - venv Setup"
|
||||
echo "============================"
|
||||
echo ""
|
||||
|
||||
# Check Python version
|
||||
PYTHON_BIN="${PYTHON_BIN:-python3}"
|
||||
PYTHON_VERSION=$("$PYTHON_BIN" --version 2>&1)
|
||||
echo "Using: $PYTHON_VERSION"
|
||||
echo "Path: $(command -v "$PYTHON_BIN" 2>/dev/null || echo "$PYTHON_BIN")"
|
||||
echo ""
|
||||
|
||||
# Remove existing venv if --force flag
|
||||
if [[ "$1" == "--force" || "$1" == "-f" ]]; then
|
||||
if [[ -d "$VENV_DIR" ]]; then
|
||||
echo "🗑️ Removing existing venv..."
|
||||
rm -rf "$VENV_DIR"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check if venv exists
|
||||
if [[ -d "$VENV_DIR" ]]; then
|
||||
echo "⚠️ venv already exists at $VENV_DIR"
|
||||
echo " Use --force to rebuild"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Create venv
|
||||
echo "📁 Creating virtual environment..."
|
||||
"$PYTHON_BIN" -m venv "$VENV_DIR"
|
||||
|
||||
# Activate venv
|
||||
source "$VENV_DIR/bin/activate"
|
||||
|
||||
# Upgrade pip
|
||||
echo "⬆️ Upgrading pip..."
|
||||
pip install --upgrade pip --quiet
|
||||
|
||||
# Install requirements
|
||||
echo "📥 Installing dependencies..."
|
||||
pip install -r "$BASE_DIR/requirements.txt" --quiet
|
||||
|
||||
# NixOS-specific: Add LD_LIBRARY_PATH to activate script
|
||||
if [[ -d "/nix/store" ]]; then
|
||||
echo "🐧 NixOS detected - configuring libstdc++ path..."
|
||||
|
||||
ACTIVATE_SCRIPT="$VENV_DIR/bin/activate"
|
||||
|
||||
# Find libstdc++ path
|
||||
LIBSTDCXX_PATH=""
|
||||
if [[ -d "/home/linuxbrew/.linuxbrew/lib" ]]; then
|
||||
LIBSTDCXX_PATH="/home/linuxbrew/.linuxbrew/lib"
|
||||
elif [[ -d "$HOME/.linuxbrew/lib" ]]; then
|
||||
LIBSTDCXX_PATH="$HOME/.linuxbrew/lib"
|
||||
else
|
||||
# Try nix store - only set if find returns a result
|
||||
GCC_LIB_DIR=$(find /nix/store -maxdepth 2 -name "*-gcc-*-lib" -print -quit 2>/dev/null)
|
||||
if [[ -n "$GCC_LIB_DIR" && -d "$GCC_LIB_DIR/lib" ]]; then
|
||||
LIBSTDCXX_PATH="$GCC_LIB_DIR/lib"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -n "$LIBSTDCXX_PATH" && -d "$LIBSTDCXX_PATH" ]]; then
|
||||
# Add to activate script if not already there
|
||||
if ! grep -q "FINANCE_NEWS_LD_LIBRARY_PATH" "$ACTIVATE_SCRIPT"; then
|
||||
cat >> "$ACTIVATE_SCRIPT" << EOF
|
||||
|
||||
# NixOS libstdc++ fix for numpy/yfinance (added by venv-setup.sh)
|
||||
if [[ -z "\${FINANCE_NEWS_LD_LIBRARY_PATH:-}" ]]; then
|
||||
export FINANCE_NEWS_LD_LIBRARY_PATH=1
|
||||
if [[ -z "\${LD_LIBRARY_PATH:-}" ]]; then
|
||||
export LD_LIBRARY_PATH="$LIBSTDCXX_PATH"
|
||||
else
|
||||
export LD_LIBRARY_PATH="$LIBSTDCXX_PATH:\$LD_LIBRARY_PATH"
|
||||
fi
|
||||
fi
|
||||
EOF
|
||||
echo " Added LD_LIBRARY_PATH=$LIBSTDCXX_PATH to activate script"
|
||||
fi
|
||||
else
|
||||
echo " ⚠️ Could not find libstdc++.so.6 path"
|
||||
echo " Install Linuxbrew: /bin/bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\""
|
||||
fi
|
||||
fi
|
||||
|
||||
# Verify installation
|
||||
echo ""
|
||||
echo "✅ venv created successfully!"
|
||||
echo ""
|
||||
echo "Verifying installation..."
|
||||
"$VENV_DIR/bin/python3" -c "import feedparser; print(' ✓ feedparser')"
|
||||
"$VENV_DIR/bin/python3" -c "import yfinance; print(' ✓ yfinance')" 2>/dev/null || echo " ⚠️ yfinance import failed (may need LD_LIBRARY_PATH)"
|
||||
|
||||
echo ""
|
||||
echo "To activate manually:"
|
||||
echo " source $VENV_DIR/bin/activate"
|
||||
echo ""
|
||||
echo "Or just use the CLI:"
|
||||
echo " ./scripts/finance-news briefing --morning"
|
||||
34
tests/README.md
Normal file
34
tests/README.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Unit Tests
|
||||
|
||||
## Setup
|
||||
|
||||
```bash
|
||||
# Install test dependencies
|
||||
pip install -r requirements-test.txt
|
||||
|
||||
# Run tests
|
||||
pytest
|
||||
|
||||
# Run with coverage
|
||||
pytest --cov=scripts --cov-report=html
|
||||
|
||||
# Run specific test file
|
||||
pytest tests/test_portfolio.py
|
||||
```
|
||||
|
||||
## Test Structure
|
||||
|
||||
- `test_portfolio.py` - Portfolio CRUD operations
|
||||
- `test_fetch_news.py` - RSS feed parsing with mocked responses
|
||||
- `test_setup.py` - Setup wizard validation
|
||||
- `fixtures/` - Sample RSS and portfolio data
|
||||
|
||||
## Coverage Target
|
||||
|
||||
60%+ coverage for core functions (portfolio, fetch_news, setup).
|
||||
|
||||
## Notes
|
||||
|
||||
- Tests use `tmp_path` for file isolation
|
||||
- Network calls are mocked with `unittest.mock`
|
||||
- `pytest-mock` provides `mocker` fixture for advanced mocking
|
||||
4
tests/fixtures/sample_portfolio.csv
vendored
Normal file
4
tests/fixtures/sample_portfolio.csv
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
symbol,name,category,notes
|
||||
AAPL,Apple Inc,Tech,Core holding
|
||||
TSLA,Tesla Inc,Auto,Growth play
|
||||
MSFT,Microsoft,Tech,Dividend stock
|
||||
|
20
tests/fixtures/sample_rss.xml
vendored
Normal file
20
tests/fixtures/sample_rss.xml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss version="2.0">
|
||||
<channel>
|
||||
<title>Test Market News</title>
|
||||
<link>https://example.com</link>
|
||||
<description>Sample RSS feed for testing</description>
|
||||
<item>
|
||||
<title>Apple Stock Rises 5%</title>
|
||||
<link>https://example.com/apple-rises</link>
|
||||
<description>Apple Inc. shares rose 5% today on strong earnings.</description>
|
||||
<pubDate>Mon, 20 Jan 2025 10:00:00 GMT</pubDate>
|
||||
</item>
|
||||
<item>
|
||||
<title>Tesla Announces New Model</title>
|
||||
<link>https://example.com/tesla-model</link>
|
||||
<description>Tesla unveils new electric vehicle model.</description>
|
||||
<pubDate>Mon, 20 Jan 2025 11:30:00 GMT</pubDate>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
110
tests/test_alerts.py
Normal file
110
tests/test_alerts.py
Normal file
@@ -0,0 +1,110 @@
|
||||
import sys
|
||||
from pathlib import Path
|
||||
import json
|
||||
import pytest
|
||||
from unittest.mock import Mock, patch
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
# Add scripts to path
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent / "scripts"))
|
||||
|
||||
from alerts import check_alerts, load_alerts, save_alerts
|
||||
|
||||
@pytest.fixture
|
||||
def mock_alerts_data():
|
||||
return {
|
||||
"_meta": {"version": 1, "supported_currencies": ["USD", "EUR"]},
|
||||
"alerts": [
|
||||
{
|
||||
"ticker": "AAPL",
|
||||
"target_price": 150.0,
|
||||
"currency": "USD",
|
||||
"note": "Buy Apple",
|
||||
"triggered_count": 0,
|
||||
"last_triggered": None
|
||||
},
|
||||
{
|
||||
"ticker": "TSLA",
|
||||
"target_price": 200.0,
|
||||
"currency": "USD",
|
||||
"note": "Buy Tesla",
|
||||
"triggered_count": 5,
|
||||
"last_triggered": "2026-01-26T10:00:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
def test_check_alerts_trigger(mock_alerts_data, monkeypatch, tmp_path):
|
||||
# Setup mock alerts file
|
||||
alerts_file = tmp_path / "alerts.json"
|
||||
monkeypatch.setattr("alerts.ALERTS_FILE", alerts_file)
|
||||
alerts_file.write_text(json.dumps(mock_alerts_data))
|
||||
|
||||
# Mock market data: AAPL is under target, TSLA is over
|
||||
mock_quotes = {
|
||||
"AAPL": {"price": 145.0},
|
||||
"TSLA": {"price": 210.0}
|
||||
}
|
||||
|
||||
with patch("alerts.get_fetch_market_data") as mock_fmd_getter:
|
||||
mock_fmd = Mock(return_value=mock_quotes)
|
||||
mock_fmd_getter.return_value = mock_fmd
|
||||
|
||||
results = check_alerts()
|
||||
|
||||
assert len(results["triggered"]) == 1
|
||||
assert results["triggered"][0]["ticker"] == "AAPL"
|
||||
assert results["triggered"][0]["current_price"] == 145.0
|
||||
|
||||
assert len(results["watching"]) == 1
|
||||
assert results["watching"][0]["ticker"] == "TSLA"
|
||||
|
||||
# Verify triggered count incremented for AAPL
|
||||
updated_data = json.loads(alerts_file.read_text())
|
||||
aapl_alert = next(a for a in updated_data["alerts"] if a["ticker"] == "AAPL")
|
||||
assert aapl_alert["triggered_count"] == 1
|
||||
assert aapl_alert["last_triggered"] is not None
|
||||
|
||||
def test_check_alerts_deduplication(mock_alerts_data, monkeypatch, tmp_path):
|
||||
# If already triggered today, triggered_count should NOT increment
|
||||
now = datetime.now()
|
||||
mock_alerts_data["alerts"][0]["last_triggered"] = now.isoformat()
|
||||
mock_alerts_data["alerts"][0]["triggered_count"] = 1
|
||||
|
||||
alerts_file = tmp_path / "alerts.json"
|
||||
monkeypatch.setattr("alerts.ALERTS_FILE", alerts_file)
|
||||
alerts_file.write_text(json.dumps(mock_alerts_data))
|
||||
|
||||
mock_quotes = {"AAPL": {"price": 140.0}, "TSLA": {"price": 250.0}}
|
||||
|
||||
with patch("alerts.get_fetch_market_data") as mock_fmd_getter:
|
||||
mock_fmd = Mock(return_value=mock_quotes)
|
||||
mock_fmd_getter.return_value = mock_fmd
|
||||
|
||||
check_alerts()
|
||||
|
||||
updated_data = json.loads(alerts_file.read_text())
|
||||
aapl_alert = next(a for a in updated_data["alerts"] if a["ticker"] == "AAPL")
|
||||
assert aapl_alert["triggered_count"] == 1 # Still 1, didn't increment because same day
|
||||
|
||||
def test_check_alerts_snooze(mock_alerts_data, monkeypatch, tmp_path):
|
||||
# Snoozed alert should be ignored
|
||||
future_date = datetime.now() + timedelta(days=1)
|
||||
mock_alerts_data["alerts"][0]["snooze_until"] = future_date.isoformat()
|
||||
|
||||
alerts_file = tmp_path / "alerts.json"
|
||||
monkeypatch.setattr("alerts.ALERTS_FILE", alerts_file)
|
||||
alerts_file.write_text(json.dumps(mock_alerts_data))
|
||||
|
||||
mock_quotes = {"AAPL": {"price": 140.0}, "TSLA": {"price": 190.0}}
|
||||
|
||||
with patch("alerts.get_fetch_market_data") as mock_fmd_getter:
|
||||
mock_fmd = Mock(return_value=mock_quotes)
|
||||
mock_fmd_getter.return_value = mock_fmd
|
||||
|
||||
results = check_alerts()
|
||||
|
||||
# AAPL is snoozed, so only TSLA should be in triggered
|
||||
assert len(results["triggered"]) == 1
|
||||
assert results["triggered"][0]["ticker"] == "TSLA"
|
||||
assert all(t["ticker"] != "AAPL" for t in results["triggered"])
|
||||
390
tests/test_alerts_extended.py
Normal file
390
tests/test_alerts_extended.py
Normal file
@@ -0,0 +1,390 @@
|
||||
"""Extended tests for alerts.py - price target alerts."""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from argparse import Namespace
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
from unittest.mock import Mock, patch
|
||||
from io import StringIO
|
||||
|
||||
import pytest
|
||||
|
||||
# Add scripts to path
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent / "scripts"))
|
||||
|
||||
from alerts import (
|
||||
load_alerts,
|
||||
save_alerts,
|
||||
get_alert_by_ticker,
|
||||
format_price,
|
||||
cmd_list,
|
||||
cmd_set,
|
||||
cmd_delete,
|
||||
cmd_snooze,
|
||||
cmd_update,
|
||||
SUPPORTED_CURRENCIES,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_alerts_data():
|
||||
"""Sample alerts data for testing."""
|
||||
return {
|
||||
"_meta": {"version": 1, "supported_currencies": SUPPORTED_CURRENCIES},
|
||||
"alerts": [
|
||||
{
|
||||
"ticker": "AAPL",
|
||||
"target_price": 150.0,
|
||||
"currency": "USD",
|
||||
"note": "Buy Apple",
|
||||
"set_by": "art",
|
||||
"set_date": "2026-01-15",
|
||||
"status": "active",
|
||||
"snooze_until": None,
|
||||
"triggered_count": 0,
|
||||
"last_triggered": None,
|
||||
},
|
||||
{
|
||||
"ticker": "TSLA",
|
||||
"target_price": 200.0,
|
||||
"currency": "USD",
|
||||
"note": "Buy Tesla",
|
||||
"set_by": "",
|
||||
"set_date": "2026-01-20",
|
||||
"status": "active",
|
||||
"snooze_until": None,
|
||||
"triggered_count": 5,
|
||||
"last_triggered": "2026-01-26T10:00:00",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def alerts_file(tmp_path, sample_alerts_data):
|
||||
"""Create a temporary alerts file."""
|
||||
alerts_path = tmp_path / "alerts.json"
|
||||
alerts_path.write_text(json.dumps(sample_alerts_data))
|
||||
return alerts_path
|
||||
|
||||
|
||||
class TestLoadAlerts:
|
||||
"""Tests for load_alerts()."""
|
||||
|
||||
def test_load_existing_file(self, alerts_file, monkeypatch):
|
||||
"""Load alerts from existing file."""
|
||||
monkeypatch.setattr("alerts.ALERTS_FILE", alerts_file)
|
||||
data = load_alerts()
|
||||
|
||||
assert "_meta" in data
|
||||
assert len(data["alerts"]) == 2
|
||||
assert data["alerts"][0]["ticker"] == "AAPL"
|
||||
|
||||
def test_load_missing_file(self, tmp_path, monkeypatch):
|
||||
"""Return default structure when file doesn't exist."""
|
||||
missing_path = tmp_path / "missing.json"
|
||||
monkeypatch.setattr("alerts.ALERTS_FILE", missing_path)
|
||||
|
||||
data = load_alerts()
|
||||
|
||||
assert data["_meta"]["version"] == 1
|
||||
assert data["alerts"] == []
|
||||
assert "supported_currencies" in data["_meta"]
|
||||
|
||||
|
||||
class TestSaveAlerts:
|
||||
"""Tests for save_alerts()."""
|
||||
|
||||
def test_save_updates_timestamp(self, tmp_path, sample_alerts_data, monkeypatch):
|
||||
"""Save should update the updated_at field."""
|
||||
alerts_path = tmp_path / "alerts.json"
|
||||
monkeypatch.setattr("alerts.ALERTS_FILE", alerts_path)
|
||||
|
||||
save_alerts(sample_alerts_data)
|
||||
|
||||
saved = json.loads(alerts_path.read_text())
|
||||
assert "updated_at" in saved["_meta"]
|
||||
|
||||
def test_save_preserves_data(self, tmp_path, sample_alerts_data, monkeypatch):
|
||||
"""Save should preserve all alert data."""
|
||||
alerts_path = tmp_path / "alerts.json"
|
||||
monkeypatch.setattr("alerts.ALERTS_FILE", alerts_path)
|
||||
|
||||
save_alerts(sample_alerts_data)
|
||||
|
||||
saved = json.loads(alerts_path.read_text())
|
||||
assert len(saved["alerts"]) == 2
|
||||
assert saved["alerts"][0]["ticker"] == "AAPL"
|
||||
|
||||
|
||||
class TestGetAlertByTicker:
|
||||
"""Tests for get_alert_by_ticker()."""
|
||||
|
||||
def test_find_existing_alert(self, sample_alerts_data):
|
||||
"""Find alert by ticker."""
|
||||
alerts = sample_alerts_data["alerts"]
|
||||
result = get_alert_by_ticker(alerts, "AAPL")
|
||||
|
||||
assert result is not None
|
||||
assert result["ticker"] == "AAPL"
|
||||
assert result["target_price"] == 150.0
|
||||
|
||||
def test_find_case_insensitive(self, sample_alerts_data):
|
||||
"""Find alert regardless of case."""
|
||||
alerts = sample_alerts_data["alerts"]
|
||||
result = get_alert_by_ticker(alerts, "aapl")
|
||||
|
||||
assert result is not None
|
||||
assert result["ticker"] == "AAPL"
|
||||
|
||||
def test_not_found_returns_none(self, sample_alerts_data):
|
||||
"""Return None for non-existent ticker."""
|
||||
alerts = sample_alerts_data["alerts"]
|
||||
result = get_alert_by_ticker(alerts, "GOOG")
|
||||
|
||||
assert result is None
|
||||
|
||||
|
||||
class TestFormatPrice:
|
||||
"""Tests for format_price()."""
|
||||
|
||||
def test_format_usd(self):
|
||||
"""Format USD price."""
|
||||
assert format_price(150.50, "USD") == "$150.50"
|
||||
assert format_price(1234.56, "USD") == "$1,234.56"
|
||||
|
||||
def test_format_eur(self):
|
||||
"""Format EUR price."""
|
||||
assert format_price(100.00, "EUR") == "€100.00"
|
||||
|
||||
def test_format_jpy(self):
|
||||
"""Format JPY without decimals."""
|
||||
assert format_price(15000, "JPY") == "¥15,000"
|
||||
|
||||
def test_format_sgd(self):
|
||||
"""Format SGD price."""
|
||||
assert format_price(50.00, "SGD") == "S$50.00"
|
||||
|
||||
def test_format_mxn(self):
|
||||
"""Format MXN price."""
|
||||
assert format_price(100.00, "MXN") == "MX$100.00"
|
||||
|
||||
def test_format_unknown_currency(self):
|
||||
"""Format unknown currency with code prefix."""
|
||||
result = format_price(100.00, "GBP")
|
||||
assert "GBP" in result
|
||||
assert "100.00" in result
|
||||
|
||||
|
||||
class TestCmdList:
|
||||
"""Tests for cmd_list()."""
|
||||
|
||||
def test_list_empty_alerts(self, tmp_path, monkeypatch, capsys):
|
||||
"""List with no alerts."""
|
||||
alerts_path = tmp_path / "alerts.json"
|
||||
alerts_path.write_text(json.dumps({"_meta": {}, "alerts": []}))
|
||||
monkeypatch.setattr("alerts.ALERTS_FILE", alerts_path)
|
||||
|
||||
cmd_list(Namespace())
|
||||
|
||||
captured = capsys.readouterr()
|
||||
assert "No price alerts set" in captured.out
|
||||
|
||||
def test_list_active_alerts(self, alerts_file, monkeypatch, capsys):
|
||||
"""List active alerts."""
|
||||
monkeypatch.setattr("alerts.ALERTS_FILE", alerts_file)
|
||||
|
||||
cmd_list(Namespace())
|
||||
|
||||
captured = capsys.readouterr()
|
||||
assert "Price Alerts" in captured.out
|
||||
assert "AAPL" in captured.out
|
||||
assert "$150.00" in captured.out
|
||||
|
||||
def test_list_snoozed_alerts(self, tmp_path, monkeypatch, capsys):
|
||||
"""List snoozed alerts separately."""
|
||||
future = (datetime.now() + timedelta(days=7)).isoformat()
|
||||
data = {
|
||||
"_meta": {},
|
||||
"alerts": [
|
||||
{"ticker": "AAPL", "target_price": 150, "currency": "USD", "snooze_until": future}
|
||||
],
|
||||
}
|
||||
alerts_path = tmp_path / "alerts.json"
|
||||
alerts_path.write_text(json.dumps(data))
|
||||
monkeypatch.setattr("alerts.ALERTS_FILE", alerts_path)
|
||||
|
||||
cmd_list(Namespace())
|
||||
|
||||
captured = capsys.readouterr()
|
||||
assert "Snoozed" in captured.out
|
||||
assert "AAPL" in captured.out
|
||||
|
||||
|
||||
class TestCmdSet:
|
||||
"""Tests for cmd_set()."""
|
||||
|
||||
def test_set_new_alert(self, alerts_file, monkeypatch, capsys):
|
||||
"""Set a new alert."""
|
||||
monkeypatch.setattr("alerts.ALERTS_FILE", alerts_file)
|
||||
|
||||
with patch("alerts.get_fetch_market_data") as mock_fmd:
|
||||
mock_fmd.return_value = Mock(return_value={"GOOG": {"price": 175.0}})
|
||||
|
||||
args = Namespace(ticker="GOOG", target=150.0, currency="USD", note="Buy Google", user="art")
|
||||
cmd_set(args)
|
||||
|
||||
captured = capsys.readouterr()
|
||||
assert "Alert set: GOOG" in captured.out
|
||||
|
||||
data = json.loads(alerts_file.read_text())
|
||||
goog = next((a for a in data["alerts"] if a["ticker"] == "GOOG"), None)
|
||||
assert goog is not None
|
||||
assert goog["target_price"] == 150.0
|
||||
|
||||
def test_set_duplicate_alert(self, alerts_file, monkeypatch, capsys):
|
||||
"""Cannot set duplicate alert."""
|
||||
monkeypatch.setattr("alerts.ALERTS_FILE", alerts_file)
|
||||
|
||||
args = Namespace(ticker="AAPL", target=140.0, currency="USD", note="", user="")
|
||||
cmd_set(args)
|
||||
|
||||
captured = capsys.readouterr()
|
||||
assert "already exists" in captured.out
|
||||
|
||||
def test_set_invalid_target(self, alerts_file, monkeypatch, capsys):
|
||||
"""Reject invalid target price."""
|
||||
monkeypatch.setattr("alerts.ALERTS_FILE", alerts_file)
|
||||
|
||||
args = Namespace(ticker="GOOG", target=-10.0, currency="USD", note="", user="")
|
||||
cmd_set(args)
|
||||
|
||||
captured = capsys.readouterr()
|
||||
assert "must be greater than 0" in captured.out
|
||||
|
||||
def test_set_invalid_currency(self, alerts_file, monkeypatch, capsys):
|
||||
"""Reject invalid currency."""
|
||||
monkeypatch.setattr("alerts.ALERTS_FILE", alerts_file)
|
||||
|
||||
args = Namespace(ticker="GOOG", target=150.0, currency="XYZ", note="", user="")
|
||||
cmd_set(args)
|
||||
|
||||
captured = capsys.readouterr()
|
||||
assert "not supported" in captured.out
|
||||
|
||||
|
||||
class TestCmdDelete:
|
||||
"""Tests for cmd_delete()."""
|
||||
|
||||
def test_delete_existing_alert(self, alerts_file, monkeypatch, capsys):
|
||||
"""Delete an existing alert."""
|
||||
monkeypatch.setattr("alerts.ALERTS_FILE", alerts_file)
|
||||
|
||||
args = Namespace(ticker="AAPL")
|
||||
cmd_delete(args)
|
||||
|
||||
captured = capsys.readouterr()
|
||||
assert "Alert deleted: AAPL" in captured.out
|
||||
|
||||
data = json.loads(alerts_file.read_text())
|
||||
assert not any(a["ticker"] == "AAPL" for a in data["alerts"])
|
||||
|
||||
def test_delete_nonexistent_alert(self, alerts_file, monkeypatch, capsys):
|
||||
"""Cannot delete non-existent alert."""
|
||||
monkeypatch.setattr("alerts.ALERTS_FILE", alerts_file)
|
||||
|
||||
args = Namespace(ticker="GOOG")
|
||||
cmd_delete(args)
|
||||
|
||||
captured = capsys.readouterr()
|
||||
assert "No alert found" in captured.out
|
||||
|
||||
|
||||
class TestCmdSnooze:
|
||||
"""Tests for cmd_snooze()."""
|
||||
|
||||
def test_snooze_alert(self, alerts_file, monkeypatch, capsys):
|
||||
"""Snooze an alert."""
|
||||
monkeypatch.setattr("alerts.ALERTS_FILE", alerts_file)
|
||||
|
||||
args = Namespace(ticker="AAPL", days=7)
|
||||
cmd_snooze(args)
|
||||
|
||||
captured = capsys.readouterr()
|
||||
assert "Alert snoozed: AAPL" in captured.out
|
||||
|
||||
data = json.loads(alerts_file.read_text())
|
||||
aapl = next(a for a in data["alerts"] if a["ticker"] == "AAPL")
|
||||
assert aapl["snooze_until"] is not None
|
||||
|
||||
def test_snooze_nonexistent_alert(self, alerts_file, monkeypatch, capsys):
|
||||
"""Cannot snooze non-existent alert."""
|
||||
monkeypatch.setattr("alerts.ALERTS_FILE", alerts_file)
|
||||
|
||||
args = Namespace(ticker="GOOG", days=7)
|
||||
cmd_snooze(args)
|
||||
|
||||
captured = capsys.readouterr()
|
||||
assert "No alert found" in captured.out
|
||||
|
||||
def test_snooze_default_days(self, alerts_file, monkeypatch, capsys):
|
||||
"""Default snooze is 7 days."""
|
||||
monkeypatch.setattr("alerts.ALERTS_FILE", alerts_file)
|
||||
|
||||
args = Namespace(ticker="AAPL", days=None)
|
||||
cmd_snooze(args)
|
||||
|
||||
captured = capsys.readouterr()
|
||||
assert "Alert snoozed" in captured.out
|
||||
|
||||
|
||||
class TestCmdUpdate:
|
||||
"""Tests for cmd_update()."""
|
||||
|
||||
def test_update_target_price(self, alerts_file, monkeypatch, capsys):
|
||||
"""Update alert target price."""
|
||||
monkeypatch.setattr("alerts.ALERTS_FILE", alerts_file)
|
||||
|
||||
args = Namespace(ticker="AAPL", target=140.0, note=None)
|
||||
cmd_update(args)
|
||||
|
||||
captured = capsys.readouterr()
|
||||
assert "Alert updated: AAPL" in captured.out
|
||||
assert "$150.00" in captured.out # Old price
|
||||
assert "$140.00" in captured.out # New price
|
||||
|
||||
data = json.loads(alerts_file.read_text())
|
||||
aapl = next(a for a in data["alerts"] if a["ticker"] == "AAPL")
|
||||
assert aapl["target_price"] == 140.0
|
||||
|
||||
def test_update_with_note(self, alerts_file, monkeypatch, capsys):
|
||||
"""Update alert with new note."""
|
||||
monkeypatch.setattr("alerts.ALERTS_FILE", alerts_file)
|
||||
|
||||
args = Namespace(ticker="AAPL", target=145.0, note="New buy zone")
|
||||
cmd_update(args)
|
||||
|
||||
data = json.loads(alerts_file.read_text())
|
||||
aapl = next(a for a in data["alerts"] if a["ticker"] == "AAPL")
|
||||
assert aapl["note"] == "New buy zone"
|
||||
|
||||
def test_update_nonexistent_alert(self, alerts_file, monkeypatch, capsys):
|
||||
"""Cannot update non-existent alert."""
|
||||
monkeypatch.setattr("alerts.ALERTS_FILE", alerts_file)
|
||||
|
||||
args = Namespace(ticker="GOOG", target=150.0, note=None)
|
||||
cmd_update(args)
|
||||
|
||||
captured = capsys.readouterr()
|
||||
assert "No alert found" in captured.out
|
||||
|
||||
def test_update_invalid_target(self, alerts_file, monkeypatch, capsys):
|
||||
"""Reject invalid target price on update."""
|
||||
monkeypatch.setattr("alerts.ALERTS_FILE", alerts_file)
|
||||
|
||||
args = Namespace(ticker="AAPL", target=-10.0, note=None)
|
||||
cmd_update(args)
|
||||
|
||||
captured = capsys.readouterr()
|
||||
assert "must be greater than 0" in captured.out
|
||||
101
tests/test_briefing.py
Normal file
101
tests/test_briefing.py
Normal file
@@ -0,0 +1,101 @@
|
||||
import sys
|
||||
from pathlib import Path
|
||||
import json
|
||||
import pytest
|
||||
from unittest.mock import Mock, patch
|
||||
import subprocess
|
||||
|
||||
# Add scripts to path
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent / "scripts"))
|
||||
|
||||
from briefing import generate_and_send
|
||||
|
||||
def test_generate_and_send_success():
|
||||
# Mock subprocess.run for summarize.py
|
||||
mock_briefing_data = {
|
||||
"macro_message": "Macro Summary",
|
||||
"portfolio_message": "Portfolio Summary",
|
||||
"summary": "Full Summary"
|
||||
}
|
||||
|
||||
with patch("briefing.subprocess.run") as mock_run:
|
||||
mock_result = Mock()
|
||||
mock_result.returncode = 0
|
||||
mock_result.stdout = json.dumps(mock_briefing_data)
|
||||
mock_run.return_value = mock_result
|
||||
|
||||
args = Mock()
|
||||
args.time = "morning"
|
||||
args.style = "briefing"
|
||||
args.lang = "en"
|
||||
args.deadline = 300
|
||||
args.fast = False
|
||||
args.llm = False
|
||||
args.debug = False
|
||||
args.json = True
|
||||
args.send = False
|
||||
|
||||
result = generate_and_send(args)
|
||||
|
||||
assert result == "Macro Summary"
|
||||
assert mock_run.called
|
||||
# Check if summarize.py was called with correct args
|
||||
call_args = mock_run.call_args[0][0]
|
||||
assert "summarize.py" in str(call_args[1])
|
||||
assert "--time" in call_args
|
||||
assert "morning" in call_args
|
||||
|
||||
def test_generate_and_send_with_whatsapp():
|
||||
mock_briefing_data = {
|
||||
"macro_message": "Macro Summary",
|
||||
"portfolio_message": "Portfolio Summary"
|
||||
}
|
||||
|
||||
with patch("briefing.subprocess.run") as mock_run, \
|
||||
patch("briefing.send_to_whatsapp") as mock_send:
|
||||
|
||||
# First call is summarize.py
|
||||
mock_result = Mock()
|
||||
mock_result.returncode = 0
|
||||
mock_result.stdout = json.dumps(mock_briefing_data)
|
||||
mock_run.return_value = mock_result
|
||||
|
||||
args = Mock()
|
||||
args.time = "evening"
|
||||
args.style = "briefing"
|
||||
args.lang = "en"
|
||||
args.deadline = None
|
||||
args.fast = True
|
||||
args.llm = False
|
||||
args.json = False
|
||||
args.send = True
|
||||
args.group = "Test Group"
|
||||
args.debug = False
|
||||
|
||||
generate_and_send(args)
|
||||
|
||||
# Check if send_to_whatsapp was called for both messages
|
||||
assert mock_send.call_count == 2
|
||||
mock_send.assert_any_call("Macro Summary", "Test Group")
|
||||
mock_send.assert_any_call("Portfolio Summary", "Test Group")
|
||||
|
||||
def test_generate_and_send_failure():
|
||||
with patch("briefing.subprocess.run") as mock_run:
|
||||
mock_result = Mock()
|
||||
mock_result.returncode = 1
|
||||
mock_result.stderr = "Error occurred"
|
||||
mock_run.return_value = mock_result
|
||||
|
||||
args = Mock()
|
||||
args.time = "morning"
|
||||
args.style = "briefing"
|
||||
args.lang = "en"
|
||||
args.deadline = None
|
||||
args.fast = False
|
||||
args.llm = False
|
||||
args.json = False
|
||||
args.send = False
|
||||
args.debug = False
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
generate_and_send(args)
|
||||
111
tests/test_earnings.py
Normal file
111
tests/test_earnings.py
Normal file
@@ -0,0 +1,111 @@
|
||||
import sys
|
||||
from pathlib import Path
|
||||
import json
|
||||
import pytest
|
||||
from unittest.mock import Mock, patch, MagicMock
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
# Add scripts to path
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent / "scripts"))
|
||||
|
||||
from earnings import (
|
||||
fetch_all_earnings_finnhub,
|
||||
get_briefing_section,
|
||||
load_earnings_cache,
|
||||
save_earnings_cache,
|
||||
refresh_earnings
|
||||
)
|
||||
|
||||
@pytest.fixture
|
||||
def mock_finnhub_response():
|
||||
return {
|
||||
"earningsCalendar": [
|
||||
{
|
||||
"symbol": "AAPL",
|
||||
"date": "2026-02-01",
|
||||
"hour": "amc",
|
||||
"epsEstimate": 1.5,
|
||||
"revenueEstimate": 100000000,
|
||||
"quarter": 1,
|
||||
"year": 2026
|
||||
},
|
||||
{
|
||||
"symbol": "TSLA",
|
||||
"date": "2026-01-27",
|
||||
"hour": "bmo",
|
||||
"epsEstimate": 0.8,
|
||||
"revenueEstimate": 25000000,
|
||||
"quarter": 4,
|
||||
"year": 2025
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
def test_fetch_earnings_finnhub_success(mock_finnhub_response):
|
||||
with patch("earnings.urlopen") as mock_urlopen:
|
||||
mock_resp = MagicMock()
|
||||
mock_resp.read.return_value = json.dumps(mock_finnhub_response).encode("utf-8")
|
||||
mock_resp.__enter__.return_value = mock_resp
|
||||
mock_urlopen.return_value = mock_resp
|
||||
|
||||
with patch("earnings.get_finnhub_key", return_value="fake_key"):
|
||||
result = fetch_all_earnings_finnhub(days_ahead=30)
|
||||
|
||||
assert "AAPL" in result
|
||||
assert result["AAPL"]["date"] == "2026-02-01"
|
||||
assert result["AAPL"]["time"] == "amc"
|
||||
assert "TSLA" in result
|
||||
assert result["TSLA"]["date"] == "2026-01-27"
|
||||
|
||||
def test_cache_logic(tmp_path, monkeypatch):
|
||||
cache_file = tmp_path / "earnings_calendar.json"
|
||||
monkeypatch.setattr("earnings.EARNINGS_CACHE", cache_file)
|
||||
monkeypatch.setattr("earnings.CACHE_DIR", tmp_path)
|
||||
|
||||
test_data = {
|
||||
"last_updated": "2026-01-27T08:00:00",
|
||||
"earnings": {"AAPL": {"date": "2026-02-01"}}
|
||||
}
|
||||
|
||||
save_earnings_cache(test_data)
|
||||
assert cache_file.exists()
|
||||
|
||||
loaded_data = load_earnings_cache()
|
||||
assert loaded_data["earnings"]["AAPL"]["date"] == "2026-02-01"
|
||||
|
||||
def test_get_briefing_section_output():
|
||||
# Mock portfolio and cache to return specific earnings
|
||||
mock_portfolio = [{"symbol": "AAPL", "name": "Apple", "category": "Tech"}]
|
||||
mock_cache = {
|
||||
"last_updated": datetime.now().isoformat(),
|
||||
"earnings": {
|
||||
"AAPL": {
|
||||
"date": datetime.now().strftime("%Y-%m-%d"),
|
||||
"time": "amc",
|
||||
"eps_estimate": 1.5
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
with patch("earnings.load_portfolio", return_value=mock_portfolio), \
|
||||
patch("earnings.load_earnings_cache", return_value=mock_cache), \
|
||||
patch("earnings.refresh_earnings", return_value=mock_cache):
|
||||
|
||||
section = get_briefing_section()
|
||||
assert "EARNINGS TODAY" in section
|
||||
assert "AAPL" in section
|
||||
assert "Apple" in section
|
||||
assert "after-close" in section
|
||||
assert "Est: $1.50" in section
|
||||
|
||||
def test_refresh_earnings_force(mock_finnhub_response):
|
||||
mock_portfolio = [{"symbol": "AAPL", "name": "Apple"}]
|
||||
|
||||
with patch("earnings.get_finnhub_key", return_value="fake_key"), \
|
||||
patch("earnings.fetch_all_earnings_finnhub", return_value={"AAPL": mock_finnhub_response["earningsCalendar"][0]}), \
|
||||
patch("earnings.save_earnings_cache") as mock_save:
|
||||
|
||||
refresh_earnings(mock_portfolio, force=True)
|
||||
assert mock_save.called
|
||||
args, _ = mock_save.call_args
|
||||
assert "AAPL" in args[0]["earnings"]
|
||||
136
tests/test_fetch_news.py
Normal file
136
tests/test_fetch_news.py
Normal file
@@ -0,0 +1,136 @@
|
||||
"""Tests for RSS feed fetching and parsing."""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent / "scripts"))
|
||||
|
||||
import json
|
||||
import pytest
|
||||
from unittest.mock import Mock, patch, MagicMock
|
||||
from fetch_news import fetch_market_data, fetch_rss, _get_best_feed_url
|
||||
from utils import clamp_timeout, compute_deadline
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_rss_content():
|
||||
"""Load sample RSS fixture."""
|
||||
fixture_path = Path(__file__).parent / "fixtures" / "sample_rss.xml"
|
||||
return fixture_path.read_bytes()
|
||||
|
||||
|
||||
def test_fetch_rss_success(sample_rss_content):
|
||||
"""Test successful RSS fetch and parse."""
|
||||
with patch("urllib.request.urlopen") as mock_urlopen:
|
||||
mock_response = MagicMock()
|
||||
mock_response.read.return_value = sample_rss_content
|
||||
mock_response.__enter__.return_value = mock_response
|
||||
mock_urlopen.return_value = mock_response
|
||||
|
||||
articles = fetch_rss("https://example.com/feed.xml", timeout=7)
|
||||
|
||||
assert len(articles) == 2
|
||||
assert articles[0]["title"] == "Apple Stock Rises 5%"
|
||||
assert articles[1]["title"] == "Tesla Announces New Model"
|
||||
assert "apple-rises" in articles[0]["link"]
|
||||
assert mock_urlopen.call_args.kwargs["timeout"] == 7
|
||||
|
||||
|
||||
def test_fetch_rss_network_error():
|
||||
"""Test RSS fetch handles network errors."""
|
||||
with patch("urllib.request.urlopen", side_effect=Exception("Network error")):
|
||||
articles = fetch_rss("https://example.com/feed.xml")
|
||||
assert articles == []
|
||||
|
||||
|
||||
def test_get_best_feed_url_priority():
|
||||
"""Test feed URL selection prioritizes 'top' key."""
|
||||
source = {
|
||||
"name": "Test Source",
|
||||
"homepage": "https://example.com",
|
||||
"top": "https://example.com/top.xml",
|
||||
"markets": "https://example.com/markets.xml"
|
||||
}
|
||||
|
||||
url = _get_best_feed_url(source)
|
||||
assert url == "https://example.com/top.xml"
|
||||
|
||||
|
||||
def test_get_best_feed_url_fallback():
|
||||
"""Test feed URL falls back to other http URLs when priority keys missing."""
|
||||
source = {
|
||||
"name": "Test Source",
|
||||
"feed": "https://example.com/feed.xml"
|
||||
}
|
||||
|
||||
url = _get_best_feed_url(source)
|
||||
assert url == "https://example.com/feed.xml"
|
||||
|
||||
|
||||
def test_get_best_feed_url_none_if_no_urls():
|
||||
"""Test returns None when no valid URLs found."""
|
||||
source = {
|
||||
"name": "Test Source",
|
||||
"enabled": True,
|
||||
"note": "No URLs here"
|
||||
}
|
||||
|
||||
url = _get_best_feed_url(source)
|
||||
assert url is None
|
||||
|
||||
|
||||
def test_get_best_feed_url_skips_non_urls():
|
||||
"""Test skips non-URL values."""
|
||||
source = {
|
||||
"name": "Test Source",
|
||||
"enabled": True,
|
||||
"count": 5,
|
||||
"rss": "https://example.com/rss.xml"
|
||||
}
|
||||
|
||||
url = _get_best_feed_url(source)
|
||||
assert url == "https://example.com/rss.xml"
|
||||
|
||||
|
||||
def test_clamp_timeout_respects_deadline(monkeypatch):
|
||||
start = 100.0
|
||||
monkeypatch.setattr("utils.time.monotonic", lambda: start)
|
||||
deadline = compute_deadline(5)
|
||||
monkeypatch.setattr("utils.time.monotonic", lambda: 103.0)
|
||||
|
||||
assert clamp_timeout(30, deadline) == 2
|
||||
|
||||
|
||||
def test_clamp_timeout_deadline_exceeded(monkeypatch):
|
||||
start = 200.0
|
||||
monkeypatch.setattr("utils.time.monotonic", lambda: start)
|
||||
deadline = compute_deadline(1)
|
||||
monkeypatch.setattr("utils.time.monotonic", lambda: 205.0)
|
||||
|
||||
with pytest.raises(TimeoutError):
|
||||
clamp_timeout(30, deadline)
|
||||
|
||||
|
||||
def test_fetch_market_data_price_fallback(monkeypatch):
|
||||
sample = {
|
||||
"price": None,
|
||||
"open": 100,
|
||||
"prev_close": 105,
|
||||
"change_percent": None,
|
||||
}
|
||||
|
||||
def fake_run(*_args, **_kwargs):
|
||||
class Result:
|
||||
returncode = 0
|
||||
stdout = json.dumps(sample)
|
||||
stderr = ""
|
||||
|
||||
return Result()
|
||||
|
||||
monkeypatch.setattr("fetch_news.OPENBB_BINARY", "/bin/openbb-quote")
|
||||
monkeypatch.setattr("fetch_news.subprocess.run", fake_run)
|
||||
|
||||
no_fallback = fetch_market_data(["^GSPC"], allow_price_fallback=False)
|
||||
assert no_fallback["^GSPC"]["price"] is None
|
||||
|
||||
with_fallback = fetch_market_data(["^GSPC"], allow_price_fallback=True)
|
||||
assert with_fallback["^GSPC"]["price"] == 100
|
||||
76
tests/test_portfolio.py
Normal file
76
tests/test_portfolio.py
Normal file
@@ -0,0 +1,76 @@
|
||||
"""Tests for portfolio operations."""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Add scripts to path for imports
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent / "scripts"))
|
||||
|
||||
import pytest
|
||||
from portfolio import load_portfolio, save_portfolio
|
||||
|
||||
|
||||
def test_load_portfolio_success(tmp_path, monkeypatch):
|
||||
"""Test loading valid portfolio CSV."""
|
||||
portfolio_file = tmp_path / "portfolio.csv"
|
||||
portfolio_file.write_text("symbol,name,category,notes,type\nAAPL,Apple,Tech,,\nTSLA,Tesla,Auto,,\n")
|
||||
|
||||
monkeypatch.setattr("portfolio.PORTFOLIO_FILE", portfolio_file)
|
||||
positions = load_portfolio()
|
||||
|
||||
assert len(positions) == 2
|
||||
assert positions[0]["symbol"] == "AAPL"
|
||||
assert positions[0]["name"] == "Apple"
|
||||
assert positions[1]["symbol"] == "TSLA"
|
||||
|
||||
|
||||
def test_load_portfolio_missing_file(tmp_path, monkeypatch):
|
||||
"""Test loading non-existent portfolio returns empty list."""
|
||||
portfolio_file = tmp_path / "nonexistent.csv"
|
||||
monkeypatch.setattr("portfolio.PORTFOLIO_FILE", portfolio_file)
|
||||
|
||||
positions = load_portfolio()
|
||||
assert positions == []
|
||||
|
||||
|
||||
def test_save_portfolio(tmp_path, monkeypatch):
|
||||
"""Test saving portfolio to CSV."""
|
||||
portfolio_file = tmp_path / "portfolio.csv"
|
||||
monkeypatch.setattr("portfolio.PORTFOLIO_FILE", portfolio_file)
|
||||
|
||||
positions = [
|
||||
{"symbol": "AAPL", "name": "Apple", "category": "Tech", "notes": "", "type": "stock"},
|
||||
{"symbol": "MSFT", "name": "Microsoft", "category": "Tech", "notes": "", "type": "stock"}
|
||||
]
|
||||
save_portfolio(positions)
|
||||
|
||||
content = portfolio_file.read_text()
|
||||
assert "symbol,name,category,notes,type" in content
|
||||
assert "AAPL" in content
|
||||
assert "MSFT" in content
|
||||
|
||||
|
||||
def test_save_empty_portfolio(tmp_path, monkeypatch):
|
||||
"""Test saving empty portfolio creates header."""
|
||||
portfolio_file = tmp_path / "portfolio.csv"
|
||||
monkeypatch.setattr("portfolio.PORTFOLIO_FILE", portfolio_file)
|
||||
|
||||
save_portfolio([])
|
||||
|
||||
content = portfolio_file.read_text()
|
||||
assert content == "symbol,name,category,notes,type\n"
|
||||
|
||||
|
||||
def test_load_portfolio_preserves_fields(tmp_path, monkeypatch):
|
||||
"""Test loading portfolio preserves all fields."""
|
||||
portfolio_file = tmp_path / "portfolio.csv"
|
||||
portfolio_file.write_text("symbol,name,category,notes,type\nAAPL,Apple Inc,Tech,Core holding,stock\n")
|
||||
monkeypatch.setattr("portfolio.PORTFOLIO_FILE", portfolio_file)
|
||||
|
||||
positions = load_portfolio()
|
||||
|
||||
assert len(positions) == 1
|
||||
assert positions[0]["symbol"] == "AAPL"
|
||||
assert positions[0]["name"] == "Apple Inc"
|
||||
assert positions[0]["category"] == "Tech"
|
||||
assert positions[0]["notes"] == "Core holding"
|
||||
assert positions[0]["type"] == "stock"
|
||||
70
tests/test_ranking.py
Normal file
70
tests/test_ranking.py
Normal file
@@ -0,0 +1,70 @@
|
||||
import sys
|
||||
from pathlib import Path
|
||||
import pytest
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
# Add scripts to path
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent / "scripts"))
|
||||
|
||||
from ranking import calculate_score, rank_headlines, classify_category
|
||||
|
||||
def test_classify_category():
|
||||
assert "macro" in classify_category("Fed signals rate cut")
|
||||
assert "equities" in classify_category("Apple earnings beat")
|
||||
assert "energy" in classify_category("Oil prices surge")
|
||||
assert "tech" in classify_category("AI chip demand remains high")
|
||||
assert "geopolitics" in classify_category("US imposes new sanctions on Russia")
|
||||
assert classify_category("Weather is nice") == ["general"]
|
||||
|
||||
def test_calculate_score_impact():
|
||||
weights = {"market_impact": 0.4, "novelty": 0.2, "breadth": 0.2, "credibility": 0.1, "diversity": 0.1}
|
||||
category_counts = {}
|
||||
|
||||
high_impact = {"title": "Fed announces emergency rate cut", "source": "Reuters", "published_at": datetime.now().isoformat()}
|
||||
low_impact = {"title": "Local coffee shop opens", "source": "Blog", "published_at": datetime.now().isoformat()}
|
||||
|
||||
score_high = calculate_score(high_impact, weights, category_counts)
|
||||
score_low = calculate_score(low_impact, weights, category_counts)
|
||||
|
||||
assert score_high > score_low
|
||||
|
||||
def test_rank_headlines_deduplication():
|
||||
headlines = [
|
||||
{"title": "Fed signals rate cut in March", "source": "WSJ"},
|
||||
{"title": "FED SIGNALS RATE CUT IN MARCH!!!", "source": "Reuters"}, # Dupe
|
||||
{"title": "Apple earnings are out", "source": "CNBC"}
|
||||
]
|
||||
|
||||
result = rank_headlines(headlines)
|
||||
|
||||
# After dedupe, we should have 2 unique headlines
|
||||
assert result["after_dedupe"] == 2
|
||||
# must_read should contain the best ones
|
||||
assert len(result["must_read"]) <= 2
|
||||
|
||||
def test_rank_headlines_sorting():
|
||||
headlines = [
|
||||
{"title": "Local news", "source": "SmallBlog", "description": "Nothing much"},
|
||||
{"title": "FED EMERGENCY RATE CUT", "source": "Bloomberg", "description": "Huge market impact"},
|
||||
{"title": "Nvidia Earnings Surprise", "source": "Reuters", "description": "AI demand surges"}
|
||||
]
|
||||
|
||||
result = rank_headlines(headlines)
|
||||
|
||||
# FED should be first due to macro impact + credibility
|
||||
assert "FED" in result["must_read"][0]["title"]
|
||||
assert "Nvidia" in result["must_read"][1]["title"]
|
||||
|
||||
def test_source_cap():
|
||||
# Test that we don't have too many items from the same source
|
||||
headlines = [
|
||||
{"title": f"Story {i}", "source": "Reuters"} for i in range(10)
|
||||
]
|
||||
|
||||
# Default source cap is 2
|
||||
result = rank_headlines(headlines)
|
||||
|
||||
reuters_in_must_read = [h for h in result["must_read"] if h["source"] == "Reuters"]
|
||||
reuters_in_scan = [h for h in result["scan"] if h["source"] == "Reuters"]
|
||||
|
||||
assert len(reuters_in_must_read) + len(reuters_in_scan) <= 2
|
||||
356
tests/test_research.py
Normal file
356
tests/test_research.py
Normal file
@@ -0,0 +1,356 @@
|
||||
"""Tests for research.py - deep research module."""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from unittest.mock import Mock, patch, MagicMock
|
||||
import subprocess
|
||||
|
||||
import pytest
|
||||
|
||||
# Add scripts to path
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent / "scripts"))
|
||||
|
||||
from research import (
|
||||
format_market_data,
|
||||
format_headlines,
|
||||
format_portfolio_news,
|
||||
gemini_available,
|
||||
research_with_gemini,
|
||||
format_raw_data_report,
|
||||
generate_research_content,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_market_data():
|
||||
"""Sample market data for testing."""
|
||||
return {
|
||||
"markets": {
|
||||
"us": {
|
||||
"name": "US Markets",
|
||||
"indices": {
|
||||
"SPY": {
|
||||
"name": "S&P 500",
|
||||
"data": {"price": 5200.50, "change_percent": 1.25}
|
||||
},
|
||||
"QQQ": {
|
||||
"name": "Nasdaq 100",
|
||||
"data": {"price": 18500.00, "change_percent": -0.50}
|
||||
}
|
||||
}
|
||||
},
|
||||
"europe": {
|
||||
"name": "European Markets",
|
||||
"indices": {
|
||||
"DAX": {
|
||||
"name": "DAX",
|
||||
"data": {"price": 18200.00, "change_percent": 0.75}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"headlines": [
|
||||
{"source": "Reuters", "title": "Fed holds rates steady", "link": "https://example.com/1"},
|
||||
{"source": "Bloomberg", "title": "Tech stocks rally", "link": "https://example.com/2"},
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_portfolio_data():
|
||||
"""Sample portfolio data for testing."""
|
||||
return {
|
||||
"stocks": {
|
||||
"AAPL": {
|
||||
"quote": {"price": 185.50, "change_percent": 2.3},
|
||||
"articles": [
|
||||
{"title": "Apple reports strong earnings", "link": "https://example.com/aapl1"},
|
||||
{"title": "iPhone sales beat expectations", "link": "https://example.com/aapl2"},
|
||||
]
|
||||
},
|
||||
"MSFT": {
|
||||
"quote": {"price": 420.00, "change_percent": -1.1},
|
||||
"articles": [
|
||||
{"title": "Microsoft cloud growth slows", "link": "https://example.com/msft1"},
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class TestFormatMarketData:
|
||||
"""Tests for format_market_data()."""
|
||||
|
||||
def test_formats_market_indices(self, sample_market_data):
|
||||
"""Format market indices with prices and changes."""
|
||||
result = format_market_data(sample_market_data)
|
||||
|
||||
assert "## Market Data" in result
|
||||
assert "### US Markets" in result
|
||||
assert "S&P 500" in result
|
||||
assert "5200.5" in result # Price (may not have trailing zero)
|
||||
assert "+1.25%" in result
|
||||
assert "📈" in result # Positive change
|
||||
|
||||
def test_shows_negative_change_emoji(self, sample_market_data):
|
||||
"""Negative changes show down emoji."""
|
||||
result = format_market_data(sample_market_data)
|
||||
|
||||
assert "Nasdaq 100" in result
|
||||
assert "-0.50%" in result
|
||||
assert "📉" in result # Negative change
|
||||
|
||||
def test_handles_empty_data(self):
|
||||
"""Handle empty market data."""
|
||||
result = format_market_data({})
|
||||
assert "## Market Data" in result
|
||||
assert "### " not in result # No region headers
|
||||
|
||||
def test_handles_missing_index_data(self):
|
||||
"""Handle indices without data."""
|
||||
data = {
|
||||
"markets": {
|
||||
"us": {
|
||||
"name": "US Markets",
|
||||
"indices": {
|
||||
"SPY": {"name": "S&P 500"} # No 'data' key
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
result = format_market_data(data)
|
||||
assert "## Market Data" in result
|
||||
# Should not crash, just skip the index
|
||||
|
||||
|
||||
class TestFormatHeadlines:
|
||||
"""Tests for format_headlines()."""
|
||||
|
||||
def test_formats_headlines_with_links(self):
|
||||
"""Format headlines with sources and links."""
|
||||
headlines = [
|
||||
{"source": "Reuters", "title": "Breaking news", "link": "https://example.com/1"},
|
||||
{"source": "Bloomberg", "title": "Market update", "link": "https://example.com/2"},
|
||||
]
|
||||
result = format_headlines(headlines)
|
||||
|
||||
assert "## Current Headlines" in result
|
||||
assert "[Reuters] Breaking news" in result
|
||||
assert "URL: https://example.com/1" in result
|
||||
assert "[Bloomberg] Market update" in result
|
||||
|
||||
def test_handles_missing_source(self):
|
||||
"""Handle headlines with missing source."""
|
||||
headlines = [{"title": "No source headline", "link": "https://example.com"}]
|
||||
result = format_headlines(headlines)
|
||||
|
||||
assert "[Unknown] No source headline" in result
|
||||
|
||||
def test_handles_missing_link(self):
|
||||
"""Handle headlines without links."""
|
||||
headlines = [{"source": "Reuters", "title": "No link"}]
|
||||
result = format_headlines(headlines)
|
||||
|
||||
assert "[Reuters] No link" in result
|
||||
assert "URL:" not in result
|
||||
|
||||
def test_limits_to_20_headlines(self):
|
||||
"""Limit output to 20 headlines max."""
|
||||
headlines = [{"source": f"Source{i}", "title": f"Title {i}"} for i in range(30)]
|
||||
result = format_headlines(headlines)
|
||||
|
||||
assert "[Source19]" in result
|
||||
assert "[Source20]" not in result
|
||||
|
||||
def test_handles_empty_list(self):
|
||||
"""Handle empty headlines list."""
|
||||
result = format_headlines([])
|
||||
assert "## Current Headlines" in result
|
||||
|
||||
|
||||
class TestFormatPortfolioNews:
|
||||
"""Tests for format_portfolio_news()."""
|
||||
|
||||
def test_formats_portfolio_stocks(self, sample_portfolio_data):
|
||||
"""Format portfolio stocks with quotes and news."""
|
||||
result = format_portfolio_news(sample_portfolio_data)
|
||||
|
||||
assert "## Portfolio Analysis" in result
|
||||
assert "### AAPL" in result
|
||||
assert "$185.5" in result # Price (may not have trailing zero)
|
||||
assert "+2.30%" in result
|
||||
assert "Apple reports strong earnings" in result
|
||||
|
||||
def test_shows_negative_changes(self, sample_portfolio_data):
|
||||
"""Show negative change percentages."""
|
||||
result = format_portfolio_news(sample_portfolio_data)
|
||||
|
||||
assert "### MSFT" in result
|
||||
assert "-1.10%" in result
|
||||
|
||||
def test_limits_articles_to_5(self):
|
||||
"""Limit articles per stock to 5."""
|
||||
data = {
|
||||
"stocks": {
|
||||
"AAPL": {
|
||||
"quote": {"price": 185.0, "change_percent": 1.0},
|
||||
"articles": [{"title": f"Article {i}"} for i in range(10)]
|
||||
}
|
||||
}
|
||||
}
|
||||
result = format_portfolio_news(data)
|
||||
|
||||
assert "Article 4" in result
|
||||
assert "Article 5" not in result
|
||||
|
||||
def test_handles_empty_stocks(self):
|
||||
"""Handle empty stocks dict."""
|
||||
result = format_portfolio_news({"stocks": {}})
|
||||
assert "## Portfolio Analysis" in result
|
||||
|
||||
|
||||
class TestGeminiAvailable:
|
||||
"""Tests for gemini_available()."""
|
||||
|
||||
def test_returns_true_when_gemini_found(self):
|
||||
"""Return True when gemini CLI is found."""
|
||||
with patch("shutil.which", return_value="/usr/local/bin/gemini"):
|
||||
assert gemini_available() is True
|
||||
|
||||
def test_returns_false_when_gemini_not_found(self):
|
||||
"""Return False when gemini CLI is not found."""
|
||||
with patch("shutil.which", return_value=None):
|
||||
assert gemini_available() is False
|
||||
|
||||
|
||||
class TestResearchWithGemini:
|
||||
"""Tests for research_with_gemini()."""
|
||||
|
||||
def test_successful_research(self):
|
||||
"""Execute gemini research successfully."""
|
||||
mock_result = Mock()
|
||||
mock_result.returncode = 0
|
||||
mock_result.stdout = "# Research Report\n\nMarket analysis..."
|
||||
|
||||
with patch("subprocess.run", return_value=mock_result) as mock_run:
|
||||
result = research_with_gemini("Market data content")
|
||||
|
||||
assert result == "# Research Report\n\nMarket analysis..."
|
||||
mock_run.assert_called_once()
|
||||
|
||||
def test_research_with_focus_areas(self):
|
||||
"""Include focus areas in prompt."""
|
||||
mock_result = Mock()
|
||||
mock_result.returncode = 0
|
||||
mock_result.stdout = "Focused analysis"
|
||||
|
||||
with patch("subprocess.run", return_value=mock_result) as mock_run:
|
||||
result = research_with_gemini("content", focus_areas=["earnings", "macro"])
|
||||
|
||||
assert result == "Focused analysis"
|
||||
# Verify focus areas were in the prompt
|
||||
call_args = mock_run.call_args[0][0]
|
||||
prompt = call_args[1]
|
||||
assert "earnings" in prompt
|
||||
assert "macro" in prompt
|
||||
|
||||
def test_handles_gemini_error(self):
|
||||
"""Handle gemini error gracefully."""
|
||||
mock_result = Mock()
|
||||
mock_result.returncode = 1
|
||||
mock_result.stderr = "API error"
|
||||
|
||||
with patch("subprocess.run", return_value=mock_result):
|
||||
result = research_with_gemini("content")
|
||||
|
||||
assert "⚠️ Gemini research error" in result
|
||||
assert "API error" in result
|
||||
|
||||
def test_handles_timeout(self):
|
||||
"""Handle subprocess timeout."""
|
||||
with patch("subprocess.run", side_effect=subprocess.TimeoutExpired(cmd="gemini", timeout=120)):
|
||||
result = research_with_gemini("content")
|
||||
|
||||
assert "⚠️ Gemini research timeout" in result
|
||||
|
||||
def test_handles_missing_gemini(self):
|
||||
"""Handle missing gemini CLI."""
|
||||
with patch("subprocess.run", side_effect=FileNotFoundError()):
|
||||
result = research_with_gemini("content")
|
||||
|
||||
assert "⚠️ Gemini CLI not found" in result
|
||||
|
||||
|
||||
class TestFormatRawDataReport:
|
||||
"""Tests for format_raw_data_report()."""
|
||||
|
||||
def test_combines_market_and_portfolio(self, sample_market_data, sample_portfolio_data):
|
||||
"""Combine market data, headlines, and portfolio."""
|
||||
result = format_raw_data_report(sample_market_data, sample_portfolio_data)
|
||||
|
||||
assert "## Market Data" in result
|
||||
assert "## Current Headlines" in result
|
||||
assert "## Portfolio Analysis" in result
|
||||
|
||||
def test_handles_no_headlines(self, sample_portfolio_data):
|
||||
"""Handle market data without headlines."""
|
||||
market_data = {"markets": {"us": {"name": "US", "indices": {}}}}
|
||||
result = format_raw_data_report(market_data, sample_portfolio_data)
|
||||
|
||||
assert "## Market Data" in result
|
||||
assert "## Current Headlines" not in result
|
||||
|
||||
def test_handles_portfolio_error(self, sample_market_data):
|
||||
"""Skip portfolio with error."""
|
||||
portfolio_data = {"error": "No portfolio configured"}
|
||||
result = format_raw_data_report(sample_market_data, portfolio_data)
|
||||
|
||||
assert "## Portfolio Analysis" not in result
|
||||
|
||||
def test_handles_empty_data(self):
|
||||
"""Handle empty market and portfolio data."""
|
||||
result = format_raw_data_report({}, {})
|
||||
assert result == ""
|
||||
|
||||
|
||||
class TestGenerateResearchContent:
|
||||
"""Tests for generate_research_content()."""
|
||||
|
||||
def test_uses_gemini_when_available(self, sample_market_data, sample_portfolio_data):
|
||||
"""Use Gemini research when available."""
|
||||
with patch("research.gemini_available", return_value=True):
|
||||
with patch("research.research_with_gemini", return_value="Gemini report") as mock_gemini:
|
||||
result = generate_research_content(sample_market_data, sample_portfolio_data)
|
||||
|
||||
assert result["report"] == "Gemini report"
|
||||
assert result["source"] == "gemini"
|
||||
mock_gemini.assert_called_once()
|
||||
|
||||
def test_falls_back_to_raw_report(self, sample_market_data, sample_portfolio_data):
|
||||
"""Fall back to raw report when Gemini unavailable."""
|
||||
with patch("research.gemini_available", return_value=False):
|
||||
result = generate_research_content(sample_market_data, sample_portfolio_data)
|
||||
|
||||
assert "## Market Data" in result["report"]
|
||||
assert result["source"] == "raw"
|
||||
|
||||
def test_handles_empty_report(self):
|
||||
"""Return empty when no data available."""
|
||||
result = generate_research_content({}, {})
|
||||
|
||||
assert result["report"] == ""
|
||||
assert result["source"] == "none"
|
||||
|
||||
def test_passes_focus_areas_to_gemini(self, sample_market_data, sample_portfolio_data):
|
||||
"""Pass focus areas to Gemini research."""
|
||||
focus = ["earnings", "tech"]
|
||||
with patch("research.gemini_available", return_value=True):
|
||||
with patch("research.research_with_gemini", return_value="Report") as mock_gemini:
|
||||
generate_research_content(sample_market_data, sample_portfolio_data, focus_areas=focus)
|
||||
|
||||
mock_gemini.assert_called_once()
|
||||
# Check that focus_areas was passed (positional or keyword)
|
||||
call_args = mock_gemini.call_args
|
||||
# Focus areas passed as second positional arg
|
||||
assert call_args[0][1] == focus or call_args.kwargs.get("focus_areas") == focus
|
||||
84
tests/test_setup.py
Normal file
84
tests/test_setup.py
Normal file
@@ -0,0 +1,84 @@
|
||||
"""Tests for setup wizard functionality."""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent / "scripts"))
|
||||
|
||||
import pytest
|
||||
import json
|
||||
from unittest.mock import patch
|
||||
from setup import load_sources, save_sources, get_default_sources, setup_language, setup_markets
|
||||
|
||||
|
||||
def test_load_sources_missing_file(tmp_path, monkeypatch):
|
||||
"""Test loading non-existent sources returns defaults."""
|
||||
sources_file = tmp_path / "sources.json"
|
||||
|
||||
# Patch both path constants to use temp file
|
||||
monkeypatch.setattr("setup.SOURCES_FILE", sources_file)
|
||||
|
||||
# File doesn't exist, so load_sources should call get_default_sources
|
||||
sources = load_sources()
|
||||
|
||||
assert isinstance(sources, dict)
|
||||
assert "rss_feeds" in sources # Default structure has rss_feeds
|
||||
|
||||
|
||||
def test_save_sources(tmp_path, monkeypatch):
|
||||
"""Test saving sources to JSON."""
|
||||
sources_file = tmp_path / "sources.json"
|
||||
monkeypatch.setattr("setup.SOURCES_FILE", sources_file)
|
||||
|
||||
sources = {
|
||||
"rss_feeds": {
|
||||
"test_source": {
|
||||
"name": "Test",
|
||||
"enabled": True,
|
||||
"top": "https://example.com/rss"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
save_sources(sources)
|
||||
|
||||
assert sources_file.exists()
|
||||
with open(sources_file) as f:
|
||||
saved = json.load(f)
|
||||
|
||||
assert saved["rss_feeds"]["test_source"]["enabled"] is True
|
||||
|
||||
|
||||
def test_get_default_sources():
|
||||
"""Test default sources structure."""
|
||||
sources = get_default_sources()
|
||||
|
||||
assert isinstance(sources, dict)
|
||||
assert "rss_feeds" in sources
|
||||
# Should have common sources like wsj, barrons, cnbc
|
||||
feeds = sources["rss_feeds"]
|
||||
assert any("wsj" in k.lower() or "barrons" in k.lower() or "cnbc" in k.lower()
|
||||
for k in feeds.keys())
|
||||
|
||||
|
||||
@patch("setup.prompt", side_effect=["en"])
|
||||
@patch("setup.save_sources")
|
||||
def test_setup_language(mock_save, mock_prompt):
|
||||
"""Test language setup function."""
|
||||
sources = {"language": {"supported": ["en", "de"], "default": "de"}}
|
||||
setup_language(sources)
|
||||
|
||||
# Should have called prompt
|
||||
mock_prompt.assert_called()
|
||||
# Language should be updated
|
||||
assert sources["language"]["default"] == "en"
|
||||
|
||||
|
||||
@patch("setup.prompt_bool", side_effect=[True, False])
|
||||
@patch("setup.save_sources")
|
||||
def test_setup_markets(mock_save, mock_prompt):
|
||||
"""Test markets setup function."""
|
||||
sources = {"markets": {"us": {"enabled": False}, "eu": {"enabled": False}}}
|
||||
setup_markets(sources)
|
||||
|
||||
# Should have prompted (at least once for US)
|
||||
assert mock_prompt.called
|
||||
286
tests/test_stocks.py
Normal file
286
tests/test_stocks.py
Normal file
@@ -0,0 +1,286 @@
|
||||
"""Tests for stocks.py - unified stock management."""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
# Add scripts to path
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent / "scripts"))
|
||||
|
||||
from stocks import (
|
||||
load_stocks,
|
||||
save_stocks,
|
||||
get_holdings,
|
||||
get_watchlist,
|
||||
get_holding_tickers,
|
||||
get_watchlist_tickers,
|
||||
add_to_watchlist,
|
||||
add_to_holdings,
|
||||
move_to_holdings,
|
||||
remove_stock,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_stocks_data():
|
||||
"""Sample stocks data for testing."""
|
||||
return {
|
||||
"version": "1.0",
|
||||
"updated": "2026-01-30",
|
||||
"holdings": [
|
||||
{"ticker": "AAPL", "name": "Apple Inc.", "category": "Tech"},
|
||||
{"ticker": "MSFT", "name": "Microsoft", "category": "Tech"},
|
||||
],
|
||||
"watchlist": [
|
||||
{"ticker": "NVDA", "target": 800.0, "notes": "Buy on dip"},
|
||||
{"ticker": "TSLA", "target": 200.0, "notes": "Watch earnings"},
|
||||
],
|
||||
"alert_definitions": {},
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def stocks_file(tmp_path, sample_stocks_data):
|
||||
"""Create a temporary stocks file."""
|
||||
stocks_path = tmp_path / "stocks.json"
|
||||
stocks_path.write_text(json.dumps(sample_stocks_data))
|
||||
return stocks_path
|
||||
|
||||
|
||||
class TestLoadStocks:
|
||||
"""Tests for load_stocks()."""
|
||||
|
||||
def test_load_existing_file(self, stocks_file, sample_stocks_data):
|
||||
"""Load stocks from existing file."""
|
||||
data = load_stocks(stocks_file)
|
||||
assert data["version"] == "1.0"
|
||||
assert len(data["holdings"]) == 2
|
||||
assert len(data["watchlist"]) == 2
|
||||
|
||||
def test_load_missing_file(self, tmp_path):
|
||||
"""Return default structure when file doesn't exist."""
|
||||
missing_path = tmp_path / "missing.json"
|
||||
data = load_stocks(missing_path)
|
||||
assert data["version"] == "1.0"
|
||||
assert data["holdings"] == []
|
||||
assert data["watchlist"] == []
|
||||
assert "alert_definitions" in data
|
||||
|
||||
|
||||
class TestSaveStocks:
|
||||
"""Tests for save_stocks()."""
|
||||
|
||||
def test_save_updates_timestamp(self, tmp_path, sample_stocks_data):
|
||||
"""Save should update the 'updated' field."""
|
||||
stocks_path = tmp_path / "stocks.json"
|
||||
save_stocks(sample_stocks_data, stocks_path)
|
||||
|
||||
saved = json.loads(stocks_path.read_text())
|
||||
assert saved["updated"] == datetime.now().strftime("%Y-%m-%d")
|
||||
|
||||
def test_save_preserves_data(self, tmp_path, sample_stocks_data):
|
||||
"""Save should preserve all data."""
|
||||
stocks_path = tmp_path / "stocks.json"
|
||||
save_stocks(sample_stocks_data, stocks_path)
|
||||
|
||||
saved = json.loads(stocks_path.read_text())
|
||||
assert len(saved["holdings"]) == 2
|
||||
assert saved["holdings"][0]["ticker"] == "AAPL"
|
||||
|
||||
|
||||
class TestGetHoldings:
|
||||
"""Tests for get_holdings()."""
|
||||
|
||||
def test_get_holdings_from_data(self, sample_stocks_data):
|
||||
"""Get holdings from provided data."""
|
||||
holdings = get_holdings(sample_stocks_data)
|
||||
assert len(holdings) == 2
|
||||
assert holdings[0]["ticker"] == "AAPL"
|
||||
|
||||
def test_get_holdings_empty(self):
|
||||
"""Return empty list for empty data."""
|
||||
data = {"holdings": [], "watchlist": []}
|
||||
holdings = get_holdings(data)
|
||||
assert holdings == []
|
||||
|
||||
|
||||
class TestGetWatchlist:
|
||||
"""Tests for get_watchlist()."""
|
||||
|
||||
def test_get_watchlist_from_data(self, sample_stocks_data):
|
||||
"""Get watchlist from provided data."""
|
||||
watchlist = get_watchlist(sample_stocks_data)
|
||||
assert len(watchlist) == 2
|
||||
assert watchlist[0]["ticker"] == "NVDA"
|
||||
|
||||
def test_get_watchlist_empty(self):
|
||||
"""Return empty list for empty data."""
|
||||
data = {"holdings": [], "watchlist": []}
|
||||
watchlist = get_watchlist(data)
|
||||
assert watchlist == []
|
||||
|
||||
|
||||
class TestGetHoldingTickers:
|
||||
"""Tests for get_holding_tickers()."""
|
||||
|
||||
def test_get_holding_tickers(self, sample_stocks_data):
|
||||
"""Get set of holding tickers."""
|
||||
tickers = get_holding_tickers(sample_stocks_data)
|
||||
assert tickers == {"AAPL", "MSFT"}
|
||||
|
||||
def test_get_holding_tickers_empty(self):
|
||||
"""Return empty set for no holdings."""
|
||||
data = {"holdings": [], "watchlist": []}
|
||||
tickers = get_holding_tickers(data)
|
||||
assert tickers == set()
|
||||
|
||||
|
||||
class TestGetWatchlistTickers:
|
||||
"""Tests for get_watchlist_tickers()."""
|
||||
|
||||
def test_get_watchlist_tickers(self, sample_stocks_data):
|
||||
"""Get set of watchlist tickers."""
|
||||
tickers = get_watchlist_tickers(sample_stocks_data)
|
||||
assert tickers == {"NVDA", "TSLA"}
|
||||
|
||||
def test_get_watchlist_tickers_empty(self):
|
||||
"""Return empty set for empty watchlist."""
|
||||
data = {"holdings": [], "watchlist": []}
|
||||
tickers = get_watchlist_tickers(data)
|
||||
assert tickers == set()
|
||||
|
||||
|
||||
class TestAddToWatchlist:
|
||||
"""Tests for add_to_watchlist()."""
|
||||
|
||||
def test_add_new_to_watchlist(self, stocks_file, monkeypatch):
|
||||
"""Add new stock to watchlist."""
|
||||
monkeypatch.setattr("stocks.STOCKS_FILE", stocks_file)
|
||||
|
||||
result = add_to_watchlist("AMD", target=150.0, notes="Watch for dip")
|
||||
assert result is True
|
||||
|
||||
data = load_stocks(stocks_file)
|
||||
tickers = get_watchlist_tickers(data)
|
||||
assert "AMD" in tickers
|
||||
|
||||
def test_update_existing_watchlist(self, stocks_file, monkeypatch):
|
||||
"""Update existing watchlist entry."""
|
||||
monkeypatch.setattr("stocks.STOCKS_FILE", stocks_file)
|
||||
|
||||
# NVDA already in watchlist with target 800
|
||||
result = add_to_watchlist("NVDA", target=750.0, notes="Updated target")
|
||||
assert result is True
|
||||
|
||||
data = load_stocks(stocks_file)
|
||||
nvda = next(w for w in data["watchlist"] if w["ticker"] == "NVDA")
|
||||
assert nvda["target"] == 750.0
|
||||
assert nvda["notes"] == "Updated target"
|
||||
|
||||
def test_add_with_alerts(self, stocks_file, monkeypatch):
|
||||
"""Add stock with alert definitions."""
|
||||
monkeypatch.setattr("stocks.STOCKS_FILE", stocks_file)
|
||||
|
||||
alerts = ["below_target", "above_stop"]
|
||||
result = add_to_watchlist("GOOG", target=180.0, alerts=alerts)
|
||||
assert result is True
|
||||
|
||||
data = load_stocks(stocks_file)
|
||||
goog = next(w for w in data["watchlist"] if w["ticker"] == "GOOG")
|
||||
assert goog["alerts"] == alerts
|
||||
|
||||
|
||||
class TestAddToHoldings:
|
||||
"""Tests for add_to_holdings()."""
|
||||
|
||||
def test_add_new_holding(self, stocks_file, monkeypatch):
|
||||
"""Add new stock to holdings."""
|
||||
monkeypatch.setattr("stocks.STOCKS_FILE", stocks_file)
|
||||
|
||||
result = add_to_holdings("GOOG", name="Alphabet", category="Tech")
|
||||
assert result is True
|
||||
|
||||
data = load_stocks(stocks_file)
|
||||
tickers = get_holding_tickers(data)
|
||||
assert "GOOG" in tickers
|
||||
|
||||
def test_update_existing_holding(self, stocks_file, monkeypatch):
|
||||
"""Update existing holding."""
|
||||
monkeypatch.setattr("stocks.STOCKS_FILE", stocks_file)
|
||||
|
||||
result = add_to_holdings("AAPL", name="Apple Inc.", category="Consumer", notes="Core holding")
|
||||
assert result is True
|
||||
|
||||
data = load_stocks(stocks_file)
|
||||
aapl = next(h for h in data["holdings"] if h["ticker"] == "AAPL")
|
||||
assert aapl["category"] == "Consumer"
|
||||
assert aapl["notes"] == "Core holding"
|
||||
|
||||
|
||||
class TestMoveToHoldings:
|
||||
"""Tests for move_to_holdings()."""
|
||||
|
||||
def test_move_from_watchlist(self, stocks_file, monkeypatch):
|
||||
"""Move stock from watchlist to holdings."""
|
||||
monkeypatch.setattr("stocks.STOCKS_FILE", stocks_file)
|
||||
|
||||
# NVDA is in watchlist, not holdings
|
||||
result = move_to_holdings("NVDA", name="NVIDIA Corp", category="Semis")
|
||||
assert result is True
|
||||
|
||||
data = load_stocks(stocks_file)
|
||||
assert "NVDA" in get_holding_tickers(data)
|
||||
assert "NVDA" not in get_watchlist_tickers(data)
|
||||
|
||||
def test_move_nonexistent_returns_false(self, stocks_file, monkeypatch):
|
||||
"""Moving non-existent ticker returns False."""
|
||||
monkeypatch.setattr("stocks.STOCKS_FILE", stocks_file)
|
||||
|
||||
result = move_to_holdings("NONEXISTENT")
|
||||
assert result is False
|
||||
|
||||
|
||||
class TestRemoveStock:
|
||||
"""Tests for remove_stock()."""
|
||||
|
||||
def test_remove_from_holdings(self, stocks_file, monkeypatch):
|
||||
"""Remove stock from holdings."""
|
||||
monkeypatch.setattr("stocks.STOCKS_FILE", stocks_file)
|
||||
|
||||
result = remove_stock("AAPL", from_list="holdings")
|
||||
assert result is True
|
||||
|
||||
data = load_stocks(stocks_file)
|
||||
assert "AAPL" not in get_holding_tickers(data)
|
||||
|
||||
def test_remove_from_watchlist(self, stocks_file, monkeypatch):
|
||||
"""Remove stock from watchlist."""
|
||||
monkeypatch.setattr("stocks.STOCKS_FILE", stocks_file)
|
||||
|
||||
result = remove_stock("NVDA", from_list="watchlist")
|
||||
assert result is True
|
||||
|
||||
data = load_stocks(stocks_file)
|
||||
assert "NVDA" not in get_watchlist_tickers(data)
|
||||
|
||||
def test_remove_nonexistent_returns_false(self, stocks_file, monkeypatch):
|
||||
"""Removing non-existent ticker returns False."""
|
||||
monkeypatch.setattr("stocks.STOCKS_FILE", stocks_file)
|
||||
|
||||
result = remove_stock("NONEXISTENT", from_list="holdings")
|
||||
assert result is False
|
||||
|
||||
def test_remove_auto_detects_list(self, stocks_file, monkeypatch):
|
||||
"""Remove without specifying list auto-detects."""
|
||||
monkeypatch.setattr("stocks.STOCKS_FILE", stocks_file)
|
||||
|
||||
# AAPL is in holdings
|
||||
result = remove_stock("AAPL")
|
||||
assert result is True
|
||||
|
||||
data = load_stocks(stocks_file)
|
||||
assert "AAPL" not in get_holding_tickers(data)
|
||||
345
tests/test_summarize.py
Normal file
345
tests/test_summarize.py
Normal file
@@ -0,0 +1,345 @@
|
||||
"""Tests for summarize helpers."""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent / "scripts"))
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
import summarize
|
||||
from summarize import (
|
||||
MoverContext,
|
||||
SectorCluster,
|
||||
WatchpointsData,
|
||||
build_watchpoints_data,
|
||||
classify_move_type,
|
||||
detect_sector_clusters,
|
||||
format_watchpoints,
|
||||
get_index_change,
|
||||
match_headline_to_symbol,
|
||||
)
|
||||
|
||||
|
||||
class FixedDateTime(datetime):
|
||||
@classmethod
|
||||
def now(cls, tz=None):
|
||||
return cls(2026, 1, 1, 15, 0)
|
||||
|
||||
|
||||
def test_generate_briefing_auto_time_evening(capsys, monkeypatch):
|
||||
def fake_market_news(*_args, **_kwargs):
|
||||
return {
|
||||
"headlines": [
|
||||
{"source": "CNBC", "title": "Headline one", "link": "https://example.com/1"},
|
||||
{"source": "Yahoo", "title": "Headline two", "link": "https://example.com/2"},
|
||||
{"source": "CNBC", "title": "Headline three", "link": "https://example.com/3"},
|
||||
],
|
||||
"markets": {
|
||||
"us": {
|
||||
"name": "US Markets",
|
||||
"indices": {
|
||||
"^GSPC": {"name": "S&P 500", "data": {"price": 100, "change_percent": 1.0}},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
def fake_summary(*_args, **_kwargs):
|
||||
return "OK"
|
||||
|
||||
monkeypatch.setattr(summarize, "get_market_news", fake_market_news)
|
||||
monkeypatch.setattr(summarize, "get_portfolio_news", lambda *_a, **_k: None)
|
||||
monkeypatch.setattr(summarize, "summarize_with_claude", fake_summary)
|
||||
monkeypatch.setattr(summarize, "datetime", FixedDateTime)
|
||||
|
||||
args = type(
|
||||
"Args",
|
||||
(),
|
||||
{
|
||||
"lang": "de",
|
||||
"style": "briefing",
|
||||
"time": None,
|
||||
"model": "claude",
|
||||
"json": False,
|
||||
"research": False,
|
||||
"deadline": None,
|
||||
"fast": False,
|
||||
"llm": False,
|
||||
"debug": False,
|
||||
},
|
||||
)()
|
||||
|
||||
summarize.generate_briefing(args)
|
||||
stdout = capsys.readouterr().out
|
||||
assert "Börsen Abend-Briefing" in stdout
|
||||
|
||||
|
||||
# --- Tests for watchpoints feature (Issue #92) ---
|
||||
|
||||
|
||||
class TestGetIndexChange:
|
||||
def test_extracts_sp500_change(self):
|
||||
market_data = {
|
||||
"markets": {
|
||||
"us": {
|
||||
"indices": {
|
||||
"^GSPC": {"data": {"change_percent": -1.5}}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
assert get_index_change(market_data) == -1.5
|
||||
|
||||
def test_returns_zero_on_missing_data(self):
|
||||
assert get_index_change({}) == 0.0
|
||||
assert get_index_change({"markets": {}}) == 0.0
|
||||
assert get_index_change({"markets": {"us": {}}}) == 0.0
|
||||
|
||||
|
||||
class TestMatchHeadlineToSymbol:
|
||||
def test_exact_symbol_match_dollar(self):
|
||||
headlines = [{"title": "Breaking: $NVDA surges on AI demand"}]
|
||||
result = match_headline_to_symbol("NVDA", "NVIDIA Corporation", headlines)
|
||||
assert result is not None
|
||||
assert "NVDA" in result["title"]
|
||||
|
||||
def test_exact_symbol_match_parens(self):
|
||||
headlines = [{"title": "Tesla (TSLA) reports record deliveries"}]
|
||||
result = match_headline_to_symbol("TSLA", "Tesla Inc", headlines)
|
||||
assert result is not None
|
||||
|
||||
def test_exact_symbol_match_word_boundary(self):
|
||||
headlines = [{"title": "AAPL announces new product line"}]
|
||||
result = match_headline_to_symbol("AAPL", "Apple Inc", headlines)
|
||||
assert result is not None
|
||||
|
||||
def test_company_name_match(self):
|
||||
headlines = [{"title": "Apple announces record iPhone sales"}]
|
||||
result = match_headline_to_symbol("AAPL", "Apple Inc", headlines)
|
||||
assert result is not None
|
||||
|
||||
def test_no_match_returns_none(self):
|
||||
headlines = [{"title": "Fed raises interest rates"}]
|
||||
result = match_headline_to_symbol("NVDA", "NVIDIA Corporation", headlines)
|
||||
assert result is None
|
||||
|
||||
def test_avoids_partial_symbol_match(self):
|
||||
# "APP" should not match "application"
|
||||
headlines = [{"title": "New application launches today"}]
|
||||
result = match_headline_to_symbol("APP", "AppLovin Corp", headlines)
|
||||
assert result is None
|
||||
|
||||
def test_empty_headlines(self):
|
||||
result = match_headline_to_symbol("NVDA", "NVIDIA", [])
|
||||
assert result is None
|
||||
|
||||
|
||||
class TestDetectSectorClusters:
|
||||
def test_detects_cluster_three_stocks_same_direction(self):
|
||||
movers = [
|
||||
{"symbol": "NVDA", "change_pct": -5.0},
|
||||
{"symbol": "AMD", "change_pct": -4.0},
|
||||
{"symbol": "INTC", "change_pct": -3.0},
|
||||
]
|
||||
portfolio_meta = {
|
||||
"NVDA": {"category": "Tech"},
|
||||
"AMD": {"category": "Tech"},
|
||||
"INTC": {"category": "Tech"},
|
||||
}
|
||||
clusters = detect_sector_clusters(movers, portfolio_meta)
|
||||
assert len(clusters) == 1
|
||||
assert clusters[0].category == "Tech"
|
||||
assert clusters[0].direction == "down"
|
||||
assert len(clusters[0].stocks) == 3
|
||||
|
||||
def test_no_cluster_if_less_than_three(self):
|
||||
movers = [
|
||||
{"symbol": "NVDA", "change_pct": -5.0},
|
||||
{"symbol": "AMD", "change_pct": -4.0},
|
||||
]
|
||||
portfolio_meta = {
|
||||
"NVDA": {"category": "Tech"},
|
||||
"AMD": {"category": "Tech"},
|
||||
}
|
||||
clusters = detect_sector_clusters(movers, portfolio_meta)
|
||||
assert len(clusters) == 0
|
||||
|
||||
def test_no_cluster_if_mixed_direction(self):
|
||||
movers = [
|
||||
{"symbol": "NVDA", "change_pct": 5.0},
|
||||
{"symbol": "AMD", "change_pct": -4.0},
|
||||
{"symbol": "INTC", "change_pct": 3.0},
|
||||
]
|
||||
portfolio_meta = {
|
||||
"NVDA": {"category": "Tech"},
|
||||
"AMD": {"category": "Tech"},
|
||||
"INTC": {"category": "Tech"},
|
||||
}
|
||||
clusters = detect_sector_clusters(movers, portfolio_meta)
|
||||
assert len(clusters) == 0
|
||||
|
||||
|
||||
class TestClassifyMoveType:
|
||||
def test_earnings_with_keyword(self):
|
||||
headline = {"title": "Company beats Q3 earnings expectations"}
|
||||
result = classify_move_type(headline, False, 5.0, 0.1)
|
||||
assert result == "earnings"
|
||||
|
||||
def test_sector_cluster(self):
|
||||
result = classify_move_type(None, True, -3.0, -0.5)
|
||||
assert result == "sector"
|
||||
|
||||
def test_market_wide(self):
|
||||
result = classify_move_type(None, False, -2.0, -2.0)
|
||||
assert result == "market_wide"
|
||||
|
||||
def test_company_specific_with_headline(self):
|
||||
headline = {"title": "Company announces acquisition"}
|
||||
result = classify_move_type(headline, False, 3.0, 0.1)
|
||||
assert result == "company_specific"
|
||||
|
||||
def test_company_specific_large_move_no_headline(self):
|
||||
result = classify_move_type(None, False, 8.0, 0.1)
|
||||
assert result == "company_specific"
|
||||
|
||||
def test_unknown_small_move_no_context(self):
|
||||
result = classify_move_type(None, False, 1.5, 0.2)
|
||||
assert result == "unknown"
|
||||
|
||||
|
||||
class TestFormatWatchpoints:
|
||||
def test_formats_sector_cluster(self):
|
||||
cluster = SectorCluster(
|
||||
category="Tech",
|
||||
stocks=[
|
||||
MoverContext("NVDA", -5.0, 100.0, "Tech", None, "sector", None),
|
||||
MoverContext("AMD", -4.0, 80.0, "Tech", None, "sector", None),
|
||||
MoverContext("INTC", -3.0, 30.0, "Tech", None, "sector", None),
|
||||
],
|
||||
avg_change=-4.0,
|
||||
direction="down",
|
||||
vs_index=-3.5,
|
||||
)
|
||||
data = WatchpointsData(
|
||||
movers=[],
|
||||
sector_clusters=[cluster],
|
||||
index_change=-0.5,
|
||||
market_wide=False,
|
||||
)
|
||||
result = format_watchpoints(data, "en", {})
|
||||
assert "Tech" in result
|
||||
assert "-4.0%" in result
|
||||
assert "vs Index" in result
|
||||
|
||||
def test_formats_individual_mover_with_headline(self):
|
||||
mover = MoverContext(
|
||||
symbol="NVDA",
|
||||
change_pct=5.0,
|
||||
price=100.0,
|
||||
category="Tech",
|
||||
matched_headline={"title": "NVIDIA reports record revenue"},
|
||||
move_type="company_specific",
|
||||
vs_index=4.5,
|
||||
)
|
||||
data = WatchpointsData(
|
||||
movers=[mover],
|
||||
sector_clusters=[],
|
||||
index_change=0.5,
|
||||
market_wide=False,
|
||||
)
|
||||
result = format_watchpoints(data, "en", {})
|
||||
assert "NVDA" in result
|
||||
assert "+5.0%" in result
|
||||
assert "record revenue" in result
|
||||
|
||||
def test_formats_market_wide_move_english(self):
|
||||
data = WatchpointsData(
|
||||
movers=[],
|
||||
sector_clusters=[],
|
||||
index_change=-2.0,
|
||||
market_wide=True,
|
||||
)
|
||||
result = format_watchpoints(data, "en", {})
|
||||
assert "Market-wide move" in result
|
||||
assert "S&P 500 fell 2.0%" in result
|
||||
|
||||
def test_formats_market_wide_move_german(self):
|
||||
data = WatchpointsData(
|
||||
movers=[],
|
||||
sector_clusters=[],
|
||||
index_change=2.5,
|
||||
market_wide=True,
|
||||
)
|
||||
result = format_watchpoints(data, "de", {})
|
||||
assert "Breite Marktbewegung" in result
|
||||
assert "stieg 2.5%" in result
|
||||
|
||||
def test_uses_label_fallbacks(self):
|
||||
mover = MoverContext(
|
||||
symbol="XYZ",
|
||||
change_pct=1.5,
|
||||
price=50.0,
|
||||
category="Other",
|
||||
matched_headline=None,
|
||||
move_type="unknown",
|
||||
vs_index=1.0,
|
||||
)
|
||||
data = WatchpointsData(
|
||||
movers=[mover],
|
||||
sector_clusters=[],
|
||||
index_change=0.5,
|
||||
market_wide=False,
|
||||
)
|
||||
labels = {"no_catalyst": " -- no news"}
|
||||
result = format_watchpoints(data, "en", labels)
|
||||
assert "XYZ" in result
|
||||
assert "no news" in result
|
||||
|
||||
|
||||
class TestBuildWatchpointsData:
|
||||
def test_builds_complete_data_structure(self):
|
||||
movers = [
|
||||
{"symbol": "NVDA", "change_pct": -5.0, "price": 100.0},
|
||||
{"symbol": "AMD", "change_pct": -4.0, "price": 80.0},
|
||||
{"symbol": "INTC", "change_pct": -3.0, "price": 30.0},
|
||||
{"symbol": "AAPL", "change_pct": 2.0, "price": 150.0},
|
||||
]
|
||||
headlines = [
|
||||
{"title": "NVIDIA reports weak guidance"},
|
||||
{"title": "Apple announces new product"},
|
||||
]
|
||||
portfolio_meta = {
|
||||
"NVDA": {"category": "Tech", "name": "NVIDIA Corporation"},
|
||||
"AMD": {"category": "Tech", "name": "Advanced Micro Devices"},
|
||||
"INTC": {"category": "Tech", "name": "Intel Corporation"},
|
||||
"AAPL": {"category": "Tech", "name": "Apple Inc"},
|
||||
}
|
||||
index_change = -0.5
|
||||
|
||||
result = build_watchpoints_data(movers, headlines, portfolio_meta, index_change)
|
||||
|
||||
# Should detect Tech sector cluster (3 losers)
|
||||
assert len(result.sector_clusters) == 1
|
||||
assert result.sector_clusters[0].category == "Tech"
|
||||
assert result.sector_clusters[0].direction == "down"
|
||||
|
||||
# All movers should be present
|
||||
assert len(result.movers) == 4
|
||||
|
||||
# NVDA should have matched headline
|
||||
nvda_mover = next(m for m in result.movers if m.symbol == "NVDA")
|
||||
assert nvda_mover.matched_headline is not None
|
||||
assert "guidance" in nvda_mover.matched_headline["title"]
|
||||
|
||||
# vs_index should be calculated
|
||||
assert nvda_mover.vs_index == -5.0 - (-0.5) # -4.5
|
||||
|
||||
def test_handles_empty_movers(self):
|
||||
result = build_watchpoints_data([], [], {}, 0.0)
|
||||
assert result.movers == []
|
||||
assert result.sector_clusters == []
|
||||
assert result.market_wide is False
|
||||
|
||||
def test_detects_market_wide_move(self):
|
||||
result = build_watchpoints_data([], [], {}, -2.0)
|
||||
assert result.market_wide is True
|
||||
97
workflows/README.md
Normal file
97
workflows/README.md
Normal file
@@ -0,0 +1,97 @@
|
||||
# Lobster Workflows
|
||||
|
||||
This directory contains [Lobster](https://github.com/openclaw/lobster) workflow definitions for the finance-news skill.
|
||||
|
||||
## Available Workflows
|
||||
|
||||
### `briefing.yaml` - Market Briefing with Approval
|
||||
|
||||
Generates a market briefing and sends to WhatsApp with an approval gate.
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
# Run via Lobster CLI
|
||||
lobster "workflows.run --file ~/projects/finance-news-openclaw-skill/workflows/briefing.yaml"
|
||||
|
||||
# With custom args
|
||||
lobster "workflows.run --file workflows/briefing.yaml --args-json '{\"time\":\"evening\",\"lang\":\"en\"}'"
|
||||
```
|
||||
|
||||
**Arguments:**
|
||||
| Arg | Default | Description |
|
||||
|-----|---------|-------------|
|
||||
| `time` | `morning` | Briefing type: `morning` or `evening` |
|
||||
| `lang` | `de` | Language: `en` or `de` |
|
||||
| `channel` | `whatsapp` | Delivery channel: `whatsapp` or `telegram` |
|
||||
| `target` | env var | Group name, JID, phone number, or Telegram chat ID |
|
||||
| `fast` | `false` | Use fast mode (shorter timeouts) |
|
||||
|
||||
**Environment Variables:**
|
||||
| Variable | Description |
|
||||
|----------|-------------|
|
||||
| `FINANCE_NEWS_CHANNEL` | Default channel: `whatsapp` or `telegram` |
|
||||
| `FINANCE_NEWS_TARGET` | Default target (group name, phone, chat ID) |
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
# WhatsApp group (default)
|
||||
lobster "workflows.run --file workflows/briefing.yaml"
|
||||
|
||||
# Telegram group
|
||||
lobster "workflows.run --file workflows/briefing.yaml --args-json '{\"channel\":\"telegram\",\"target\":\"-1001234567890\"}'"
|
||||
|
||||
# WhatsApp DM to phone number
|
||||
lobster "workflows.run --file workflows/briefing.yaml --args-json '{\"target\":\"+15551234567\"}'"
|
||||
|
||||
# Telegram DM to user
|
||||
lobster "workflows.run --file workflows/briefing.yaml --args-json '{\"channel\":\"telegram\",\"target\":\"@username\"}'"
|
||||
```
|
||||
|
||||
**Flow:**
|
||||
1. **Generate** - Runs Docker container to produce briefing JSON
|
||||
2. **Approve** - Halts for human review (shows briefing preview)
|
||||
3. **Send** - Delivers to channel (WhatsApp/Telegram) after approval
|
||||
|
||||
**Requirements:**
|
||||
- Docker with `finance-news-briefing` image built
|
||||
- `jq` for JSON parsing
|
||||
- `openclaw` CLI for message delivery
|
||||
|
||||
## Adding to Lobster Registry
|
||||
|
||||
To make these workflows available as named workflows in Lobster:
|
||||
|
||||
```typescript
|
||||
// In lobster/src/workflows/registry.ts
|
||||
export const workflowRegistry = {
|
||||
// ... existing workflows
|
||||
'finance.briefing': {
|
||||
name: 'finance.briefing',
|
||||
description: 'Generate market briefing with approval gate for WhatsApp/Telegram',
|
||||
argsSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
time: { type: 'string', enum: ['morning', 'evening'], default: 'morning' },
|
||||
lang: { type: 'string', enum: ['en', 'de'], default: 'de' },
|
||||
channel: { type: 'string', enum: ['whatsapp', 'telegram'], default: 'whatsapp' },
|
||||
target: { type: 'string', description: 'Group name, JID, phone, or chat ID' },
|
||||
fast: { type: 'boolean', default: false },
|
||||
},
|
||||
},
|
||||
examples: [
|
||||
{ args: { time: 'morning', lang: 'de' }, description: 'German morning briefing to WhatsApp' },
|
||||
{ args: { channel: 'telegram', target: '-1001234567890' }, description: 'Send to Telegram group' },
|
||||
],
|
||||
sideEffects: ['message.send'],
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## Why Lobster?
|
||||
|
||||
Using Lobster instead of direct cron execution provides:
|
||||
|
||||
- **Approval gates** - Review briefing before it's sent
|
||||
- **Resumability** - If interrupted, continue from last step
|
||||
- **Token efficiency** - One workflow call vs. multiple LLM tool calls
|
||||
- **Determinism** - Same inputs = same outputs
|
||||
45
workflows/alerts-cron.yaml
Normal file
45
workflows/alerts-cron.yaml
Normal file
@@ -0,0 +1,45 @@
|
||||
# Price Alerts Workflow for Cron (No Approval Gate)
|
||||
# Usage: lobster run --file workflows/alerts-cron.yaml --args-json '{"lang":"en"}'
|
||||
#
|
||||
# Schedule: 2:00 PM PT / 5:00 PM ET (1 hour after market close)
|
||||
# Checks price alerts against current prices (including after-hours)
|
||||
|
||||
name: finance.alerts.cron
|
||||
description: Check price alerts and send triggered alerts to WhatsApp/Telegram
|
||||
|
||||
args:
|
||||
lang:
|
||||
default: en
|
||||
description: "Language: en or de"
|
||||
channel:
|
||||
default: "${FINANCE_NEWS_CHANNEL:-whatsapp}"
|
||||
description: "Delivery channel: whatsapp or telegram"
|
||||
target:
|
||||
default: "${FINANCE_NEWS_TARGET}"
|
||||
description: "Target: group name, JID, or chat ID"
|
||||
|
||||
steps:
|
||||
# Check alerts against current prices
|
||||
- id: check_alerts
|
||||
command: |
|
||||
SKILL_DIR="${SKILL_DIR:-$HOME/projects/skills/personal/finance-news}"
|
||||
python3 "$SKILL_DIR/scripts/alerts.py" check --lang "${lang}"
|
||||
description: Check price alerts against current prices
|
||||
|
||||
# Send alert message if there's content
|
||||
- id: send_alerts
|
||||
command: |
|
||||
MSG=$(cat)
|
||||
MSG=$(echo "$MSG" | tr -d '\r')
|
||||
# Only send if message has actual content (not just "No price data" message)
|
||||
if echo "$MSG" | grep -q "IN BUY ZONE\|IN KAUFZONE\|WATCHING\|BEOBACHTUNG"; then
|
||||
openclaw message send \
|
||||
--channel "${channel}" \
|
||||
--target "${target}" \
|
||||
--message "$MSG"
|
||||
echo "Sent price alerts to ${channel}"
|
||||
else
|
||||
echo "No triggered alerts or watchlist items to send"
|
||||
fi
|
||||
stdin: $check_alerts.stdout
|
||||
description: Send price alerts to channel
|
||||
101
workflows/briefing-cron.yaml
Normal file
101
workflows/briefing-cron.yaml
Normal file
@@ -0,0 +1,101 @@
|
||||
# Finance Briefing Workflow for Cron (No Approval Gate)
|
||||
# Usage: lobster run --file workflows/briefing-cron.yaml --args-json '{"time":"morning","lang":"de"}'
|
||||
#
|
||||
# This workflow:
|
||||
# 1. Generates a market briefing via Docker
|
||||
# 2. Translates portfolio headlines (German)
|
||||
# 3. Sends directly to messaging channel (no approval)
|
||||
|
||||
name: finance.briefing.cron
|
||||
description: Generate market briefing and send to WhatsApp/Telegram (auto-approve for cron)
|
||||
|
||||
args:
|
||||
time:
|
||||
default: morning
|
||||
description: "Briefing type: morning or evening"
|
||||
lang:
|
||||
default: de
|
||||
description: "Language: en or de"
|
||||
channel:
|
||||
default: "${FINANCE_NEWS_CHANNEL:-whatsapp}"
|
||||
description: "Delivery channel: whatsapp or telegram"
|
||||
target:
|
||||
default: "${FINANCE_NEWS_TARGET}"
|
||||
description: "Target: group name, JID, phone number, or Telegram chat ID (requires FINANCE_NEWS_TARGET env var if not specified)"
|
||||
fast:
|
||||
default: "false"
|
||||
description: "Use fast mode: true or false"
|
||||
|
||||
steps:
|
||||
# Generate briefing and save to temp file
|
||||
- id: generate
|
||||
command: |
|
||||
SKILL_DIR="${SKILL_DIR:-$HOME/projects/skills/personal/finance-news}"
|
||||
FAST_FLAG=""
|
||||
if [ "${fast}" = "true" ]; then FAST_FLAG="--fast"; fi
|
||||
OUTFILE="/tmp/lobster-briefing-$$.json"
|
||||
# Resolve openbb-quote symlink for Docker mount
|
||||
OPENBB_BIN=$(realpath "$HOME/.local/bin/openbb-quote" 2>/dev/null || echo "")
|
||||
OPENBB_MOUNT=""
|
||||
if [ -f "$OPENBB_BIN" ]; then
|
||||
OPENBB_MOUNT="-v $OPENBB_BIN:/usr/local/bin/openbb-quote:ro"
|
||||
fi
|
||||
docker run --rm \
|
||||
-v "$SKILL_DIR/config:/app/config:ro" \
|
||||
-v "$SKILL_DIR/scripts:/app/scripts:ro" \
|
||||
$OPENBB_MOUNT \
|
||||
finance-news-briefing python3 scripts/briefing.py \
|
||||
--time "${time}" \
|
||||
--lang "${lang}" \
|
||||
--json \
|
||||
$FAST_FLAG > "$OUTFILE"
|
||||
# Output the file path for subsequent steps
|
||||
echo "$OUTFILE"
|
||||
description: Generate briefing via Docker
|
||||
|
||||
# Translate portfolio headlines (if German)
|
||||
- id: translate
|
||||
command: |
|
||||
OUTFILE=$(cat)
|
||||
OUTFILE=$(echo "$OUTFILE" | tr -d '\n')
|
||||
SKILL_DIR="${SKILL_DIR:-$HOME/projects/skills/personal/finance-news}"
|
||||
if [ "${lang}" = "de" ]; then
|
||||
python3 "$SKILL_DIR/scripts/translate_portfolio.py" "$OUTFILE" --lang de || true
|
||||
fi
|
||||
echo "$OUTFILE"
|
||||
stdin: $generate.stdout
|
||||
description: Translate portfolio headlines via openclaw
|
||||
|
||||
# Send macro briefing (market overview) - NO APPROVAL GATE
|
||||
- id: send_macro
|
||||
command: |
|
||||
OUTFILE=$(cat)
|
||||
OUTFILE=$(echo "$OUTFILE" | tr -d '\n')
|
||||
MSG=$(jq -r '.macro_message // empty' "$OUTFILE")
|
||||
if [ -n "$MSG" ]; then
|
||||
openclaw message send \
|
||||
--channel "${channel}" \
|
||||
--target "${target}" \
|
||||
--message "$MSG"
|
||||
else
|
||||
echo "No macro message to send"
|
||||
fi
|
||||
stdin: $translate.stdout
|
||||
description: Send macro briefing
|
||||
|
||||
# Send portfolio briefing (stock movers)
|
||||
- id: send_portfolio
|
||||
command: |
|
||||
OUTFILE=$(cat)
|
||||
OUTFILE=$(echo "$OUTFILE" | tr -d '\n')
|
||||
MSG=$(jq -r '.portfolio_message // empty' "$OUTFILE")
|
||||
if [ -n "$MSG" ]; then
|
||||
openclaw message send \
|
||||
--channel "${channel}" \
|
||||
--target "${target}" \
|
||||
--message "$MSG"
|
||||
else
|
||||
echo "No portfolio message to send"
|
||||
fi
|
||||
stdin: $translate.stdout
|
||||
description: Send portfolio briefing
|
||||
115
workflows/briefing.yaml
Normal file
115
workflows/briefing.yaml
Normal file
@@ -0,0 +1,115 @@
|
||||
# Finance Briefing Workflow for Lobster
|
||||
# Usage: lobster "workflows.run --file workflows/briefing.yaml --args-json '{\"time\":\"morning\",\"lang\":\"de\"}'"
|
||||
#
|
||||
# This workflow:
|
||||
# 1. Generates a market briefing via Docker
|
||||
# 2. Halts for approval before sending
|
||||
# 3. Sends to messaging channel after approval
|
||||
|
||||
name: finance.briefing
|
||||
description: Generate market briefing and send to WhatsApp/Telegram with approval gate
|
||||
|
||||
args:
|
||||
time:
|
||||
default: morning
|
||||
description: "Briefing type: morning or evening"
|
||||
lang:
|
||||
default: de
|
||||
description: "Language: en or de"
|
||||
channel:
|
||||
default: "${FINANCE_NEWS_CHANNEL:-whatsapp}"
|
||||
description: "Delivery channel: whatsapp or telegram"
|
||||
target:
|
||||
default: "${FINANCE_NEWS_TARGET}"
|
||||
description: "Target: group name, JID, phone number, or Telegram chat ID (requires FINANCE_NEWS_TARGET env var if not specified)"
|
||||
fast:
|
||||
default: "false"
|
||||
description: "Use fast mode: true or false"
|
||||
|
||||
steps:
|
||||
# Generate briefing and save to temp file
|
||||
- id: generate
|
||||
command: |
|
||||
SKILL_DIR="${SKILL_DIR:-$HOME/projects/skills/personal/finance-news}"
|
||||
FAST_FLAG=""
|
||||
if [ "${fast}" = "true" ]; then FAST_FLAG="--fast"; fi
|
||||
OUTFILE="/tmp/lobster-briefing-$$.json"
|
||||
# Resolve openbb-quote symlink for Docker mount
|
||||
OPENBB_BIN=$(realpath "$HOME/.local/bin/openbb-quote" 2>/dev/null || echo "")
|
||||
OPENBB_MOUNT=""
|
||||
if [ -f "$OPENBB_BIN" ]; then
|
||||
OPENBB_MOUNT="-v $OPENBB_BIN:/usr/local/bin/openbb-quote:ro"
|
||||
fi
|
||||
docker run --rm \
|
||||
-v "$SKILL_DIR/config:/app/config:ro" \
|
||||
-v "$SKILL_DIR/scripts:/app/scripts:ro" \
|
||||
$OPENBB_MOUNT \
|
||||
finance-news-briefing python3 scripts/briefing.py \
|
||||
--time "${time}" \
|
||||
--lang "${lang}" \
|
||||
--json \
|
||||
$FAST_FLAG > "$OUTFILE"
|
||||
# Output the file path for subsequent steps
|
||||
echo "$OUTFILE"
|
||||
description: Generate briefing via Docker
|
||||
|
||||
# Translate portfolio headlines (if German)
|
||||
- id: translate
|
||||
command: |
|
||||
OUTFILE=$(cat)
|
||||
OUTFILE=$(echo "$OUTFILE" | tr -d '\n')
|
||||
SKILL_DIR="${SKILL_DIR:-$HOME/projects/skills/personal/finance-news}"
|
||||
if [ "${lang}" = "de" ]; then
|
||||
python3 "$SKILL_DIR/scripts/translate_portfolio.py" "$OUTFILE" --lang de || true
|
||||
fi
|
||||
echo "$OUTFILE"
|
||||
stdin: $generate.stdout
|
||||
description: Translate portfolio headlines via openclaw
|
||||
|
||||
# Approval gate - workflow halts here until user approves
|
||||
- id: approve
|
||||
approval: required
|
||||
command: |
|
||||
OUTFILE=$(cat)
|
||||
echo "Briefing saved to: $OUTFILE"
|
||||
echo "Target: ${target}"
|
||||
echo "Channel: ${channel}"
|
||||
cat "$OUTFILE" | jq -r '.macro_message' | head -20
|
||||
echo "..."
|
||||
echo "Review above. Approve to send."
|
||||
stdin: $translate.stdout
|
||||
description: Approval gate before message delivery
|
||||
|
||||
# Send macro briefing (market overview)
|
||||
- id: send_macro
|
||||
command: |
|
||||
OUTFILE=$(cat)
|
||||
OUTFILE=$(echo "$OUTFILE" | tr -d '\n')
|
||||
MSG=$(jq -r '.macro_message // empty' "$OUTFILE")
|
||||
if [ -n "$MSG" ]; then
|
||||
openclaw message send \
|
||||
--channel "${channel}" \
|
||||
--target "${target}" \
|
||||
--message "$MSG"
|
||||
else
|
||||
echo "No macro message to send"
|
||||
fi
|
||||
stdin: $translate.stdout
|
||||
description: Send macro briefing
|
||||
|
||||
# Send portfolio briefing (stock movers)
|
||||
- id: send_portfolio
|
||||
command: |
|
||||
OUTFILE=$(cat)
|
||||
OUTFILE=$(echo "$OUTFILE" | tr -d '\n')
|
||||
MSG=$(jq -r '.portfolio_message // empty' "$OUTFILE")
|
||||
if [ -n "$MSG" ]; then
|
||||
openclaw message send \
|
||||
--channel "${channel}" \
|
||||
--target "${target}" \
|
||||
--message "$MSG"
|
||||
else
|
||||
echo "No portfolio message to send"
|
||||
fi
|
||||
stdin: $translate.stdout
|
||||
description: Send portfolio briefing
|
||||
45
workflows/earnings-cron.yaml
Normal file
45
workflows/earnings-cron.yaml
Normal file
@@ -0,0 +1,45 @@
|
||||
# Earnings Alert Workflow for Cron (No Approval Gate)
|
||||
# Usage: lobster run --file workflows/earnings-cron.yaml --args-json '{"lang":"en"}'
|
||||
#
|
||||
# Schedule: 6:00 AM PT / 9:00 AM ET (30 min before market open)
|
||||
# Sends today's earnings calendar to WhatsApp/Telegram
|
||||
|
||||
name: finance.earnings.cron
|
||||
description: Send earnings alerts for today's reports
|
||||
|
||||
args:
|
||||
lang:
|
||||
default: en
|
||||
description: "Language: en or de"
|
||||
channel:
|
||||
default: "${FINANCE_NEWS_CHANNEL:-whatsapp}"
|
||||
description: "Delivery channel: whatsapp or telegram"
|
||||
target:
|
||||
default: "${FINANCE_NEWS_TARGET}"
|
||||
description: "Target: group name, JID, or chat ID"
|
||||
|
||||
steps:
|
||||
# Check earnings calendar for today and this week
|
||||
- id: check_earnings
|
||||
command: |
|
||||
SKILL_DIR="${SKILL_DIR:-$HOME/projects/skills/personal/finance-news}"
|
||||
python3 "$SKILL_DIR/scripts/earnings.py" check --lang "${lang}"
|
||||
description: Get today's earnings calendar
|
||||
|
||||
# Send earnings alert if there's content
|
||||
- id: send_earnings
|
||||
command: |
|
||||
MSG=$(cat)
|
||||
MSG=$(echo "$MSG" | tr -d '\r')
|
||||
# Only send if there are actual earnings today
|
||||
if echo "$MSG" | grep -q "EARNINGS TODAY\|EARNINGS HEUTE"; then
|
||||
openclaw message send \
|
||||
--channel "${channel}" \
|
||||
--target "${target}" \
|
||||
--message "$MSG"
|
||||
echo "Sent earnings alert to ${channel}"
|
||||
else
|
||||
echo "No earnings today - skipping message"
|
||||
fi
|
||||
stdin: $check_earnings.stdout
|
||||
description: Send earnings alert to channel
|
||||
45
workflows/earnings-weekly-cron.yaml
Normal file
45
workflows/earnings-weekly-cron.yaml
Normal file
@@ -0,0 +1,45 @@
|
||||
# Weekly Earnings Alert Workflow for Cron (No Approval Gate)
|
||||
# Usage: lobster run --file workflows/earnings-weekly-cron.yaml --args-json '{"lang":"en"}'
|
||||
#
|
||||
# Schedule: Sunday 7:00 AM PT (before market week starts)
|
||||
# Sends upcoming week's earnings calendar to WhatsApp/Telegram
|
||||
|
||||
name: finance.earnings.weekly.cron
|
||||
description: Send weekly earnings preview for portfolio stocks
|
||||
|
||||
args:
|
||||
lang:
|
||||
default: en
|
||||
description: "Language: en or de"
|
||||
channel:
|
||||
default: "${FINANCE_NEWS_CHANNEL:-whatsapp}"
|
||||
description: "Delivery channel: whatsapp or telegram"
|
||||
target:
|
||||
default: "${FINANCE_NEWS_TARGET}"
|
||||
description: "Target: group name, JID, or chat ID"
|
||||
|
||||
steps:
|
||||
# Check earnings calendar for upcoming week
|
||||
- id: check_earnings
|
||||
command: |
|
||||
SKILL_DIR="${SKILL_DIR:-$HOME/projects/skills/personal/finance-news}"
|
||||
python3 "$SKILL_DIR/scripts/earnings.py" check --week --lang "${lang}"
|
||||
description: Get upcoming week's earnings calendar
|
||||
|
||||
# Send earnings alert if there's content
|
||||
- id: send_earnings
|
||||
command: |
|
||||
MSG=$(cat)
|
||||
MSG=$(echo "$MSG" | tr -d '\r')
|
||||
# Only send if there are actual earnings next week
|
||||
if echo "$MSG" | grep -qE "EARNINGS (NEXT WEEK|NÄCHSTE WOCHE)"; then
|
||||
openclaw message send \
|
||||
--channel "${channel}" \
|
||||
--target "${target}" \
|
||||
--message "$MSG"
|
||||
echo "Sent weekly earnings preview to ${channel}"
|
||||
else
|
||||
echo "No earnings next week - skipping message"
|
||||
fi
|
||||
stdin: $check_earnings.stdout
|
||||
description: Send weekly earnings alert to channel
|
||||
Reference in New Issue
Block a user