Stock Market Daily Brief — Backend Specification
Project: Market Brief Pipeline
Author: Wadsworth (Chief of Staff)
Target: Socrates 🧠 (Backend Architect)
Date: 2026-04-24
Status: DRAFT — Pending Director Approval
Updated: 2026-04-24 — Added Altruist integration, Finnhub, updated delivery time
1. Purpose
Deliver a daily stock market brief to Matt via Telegram DM at 7:30 AM CST on weekdays. The brief covers owned positions and a watchlist, including price action, technical indicators, and headline sentiment. All LLM inference runs on-premises (Gaming PC 3080 Ti) to honor the Sovereign Constraint. Portfolio data auto-syncs from Altruist when API access is granted.
2. Sovereign Architecture
| Layer | Tool | Endpoint | Cloud? |
|---|---|---|---|
| Positions + Cost Basis | Altruist Open API (OAuth 2.0) | Beelink → Altruist servers | HTTPS, OAuth (if granted; manual fallback) |
| Market Data | yfinance (Python) |
Beelink script → Yahoo Finance public API | Free, no key |
| Headlines + Pre-scored Sentiment | Finnhub free tier | Beelink → Finnhub REST API | Free API key (60 calls/min) |
| Technicals | pandas-ta |
Beelink (calculated locally) | No |
| Deep Sentiment (2nd opinion) | phi4:14b |
http://matt-pc.tail864e81.ts.net:11434/v1 |
Local |
| Brief Composition | gemma4:latest |
http://matt-pc.tail864e81.ts.net:11434/v1 |
Local |
| Delivery | OpenClaw cron → Telegram DM | Beelink | No |
No cloud LLM calls in the daily pipeline. External calls are limited to: market data (public APIs), Finnhub (free tier), and Altruist (if granted). All reasoning stays local.
3. Data Model
3.1 Portfolio Config
File: memory/stock-portfolio.json (editable by Matt/Wadsworth)
Data source priority:
1. Altruist API (if access granted) — auto-syncs positions, cost basis, transactions daily
2. Manual config (fallback) — Matt provides tickers, shares, cost basis
{
"data_source": "manual",
"altruist": {
"enabled": false,
"oauth_client_id": "",
"oauth_client_secret": "",
"token_file": "memory/secrets/altruist-token.json",
"note": "Contact Altruist support for personal API access. See: developer.altruist.com"
},
"owned": [
{
"ticker": "AAPL",
"shares": 50,
"cost_basis": 142.30,
"notes": "Core holding"
}
],
"watchlist": [
{
"ticker": "TSLA",
"notes": "Earnings April 23"
}
],
"settings": {
"delivery_time": "07:30",
"timezone": "America/Chicago",
"technicals": ["rsi_14", "macd", "sma_50", "sma_200", "volume_vs_avg"],
"sentiment_model": "phi4:14b",
"brief_model": "gemma4:latest",
"deep_analysis_model": "ollama/glm-5.1:cloud",
"local_ollama_endpoint": "http://matt-pc.tail864e81.ts.net:11434/v1",
"finnhub_api_key_file": "memory/secrets/finnhub-key.txt"
}
}
Matt will provide actual tickers (or grant Altruist API access for auto-sync). Schema should validate on load and fail gracefully if Gaming PC is unreachable.
3.2 Brief Output Schema
{
"date": "2026-04-24",
"market_summary": {
"sp500_change": "+0.4%",
"nasdaq_change": "+0.7%",
"vix": 14.2
},
"positions": [
{
"ticker": "AAPL",
"price": 192.45,
"change": "+1.23 (+0.64%)",
"technicals": {
"rsi_14": 58.2,
"macd": "bullish crossover",
"sma_50": "above",
"sma_200": "above",
"volume_vs_avg": "1.1x"
},
"sentiment": {
"finnhub_score": "bullish",
"finnhub_confidence": 0.72,
"local_score": "bullish",
"local_confidence": 0.68,
"divergence": false,
"headline_summary": "Strong services revenue; tariff concerns lingering",
"headlines_analyzed": 5
},
"pnl": {
"daily": "+$61.50",
"total": "+$2,507.50 (+35.2%)"
}
}
],
"watchlist": [
{
"ticker": "TSLA",
"price": 245.10,
"change": "-3.40 (-1.37%)",
"technicals": { "..." : "..." },
"sentiment": {
"finnhub_score": "bearish",
"finnhub_confidence": 0.65,
"local_score": "bearish",
"local_confidence": 0.60,
"divergence": false,
"headline_summary": "Delivery miss; China competition intensifying",
"headlines_analyzed": 4
}
}
],
"finnhub_extras": {
"recommendations": { "AAPL": "buy", "TSLA": "hold" },
"price_targets": { "AAPL": { "high": 250, "mean": 210, "low": 170 }},
"pattern_recognition": { "AAPL": "bullish_flag", "TSLA": "head_and_shoulders" },
"earnings_calendar": [ { "ticker": "MSFT", "date": "2026-04-25" } ]
},
"alerts": [
"AAPL: RSI approaching overbought (68.5)",
"TSLA: Broke below 50-day SMA"
]
}
4. Pipeline Stages
Stage 1: Data Collection (Beelink)
scripts/stock_brief.py --collect
- If Altruist API enabled: Fetch positions, cost basis, and transactions via Altruist Open API (OAuth 2.0)
- Endpoints:
/v2/accounts,/v2/accounts/{id}/positions,/v2/accounts/{id}/cost-basis - Merge into portfolio config (Altruist is source of truth when enabled)
- Cache OAuth token in
memory/secrets/altruist-token.json, auto-refresh on expiry - Fetch quotes for all tickers via
yfinance - Calculate technicals via
pandas-ta - Fetch from Finnhub free tier API (primary headline/sentiment source):
/company-news— recent headlines per ticker/news-sentiment— pre-computed buzz score, bearish/bullish %, weekly change/stock/recommendation— analyst buy/hold/sell consensus/stock/price-target— analyst price targets (high/mean/low)/stock/upgrade-downgrade— recent analyst changes/calendar/earnings— upcoming earnings within 2 weeks/stock/candle— OHLC for pattern recognition (if needed)- Fallback: Yahoo Finance news RSS if Finnhub rate-limited or key missing
- Write raw data to
memory/stock-data-cache.json
Error handling: If yfinance fails, fall back to cached previous day. If Finnhub rate-limited, fall back to Yahoo RSS. Log all failures. Never silently serve stale data without noting it.
Stage 2: Sentiment Enrichment (Gaming PC, Optional)
scripts/stock_brief.py --sentiment
Finnhub already provides pre-scored sentiment (buzz score, bearish/bullish %, weekly change). This is the primary sentiment source — no LLM needed for basic scoring.
Optional deep sentiment (run when Gaming PC is available):
- For each ticker, send top 5-10 headlines to phi4:14b via Ollama API
- Prompt: structured extraction — score (bullish/bearish/neutral), confidence (0-1), one-line summary
- Compare local model sentiment vs. Finnhub pre-scored sentiment
- If they diverge significantly, flag it in the brief: "Finnhub: Bullish (70%), Local: Bearish (65%) — divergent signals ⚠️"
- Batch requests to avoid GPU contention (max 2 concurrent)
- 30s timeout per request; skip local sentiment if Gaming PC is down (Finnhub scores still used)
Prompt template:
Analyze these headlines for {TICKER} and return JSON:
{"score": "bullish|bearish|neutral", "confidence": 0.0-1.0, "summary": "one line"}
Headlines:
1. {headline_1}
2. {headline_2}
...
Stage 3: Brief Composition (Gaming PC)
scripts/stock_brief.py --compose
- Send full structured data to
gemma4:latest - Prompt: compose a concise, scannable brief in Markdown suitable for Telegram
- Include: market overview, position table, watchlist, alerts, sentiment highlights, Finnhub extras (analyst recs, price targets, earnings)
- Output:
memory/stock-brief-latest.md
Formatting constraints:
- Telegram-friendly Markdown (no tables — use monospace blocks)
- Emoji indicators: 🟢 bullish, 🔴 bearish, 🟡 neutral
- Keep under 4096 chars (Telegram message limit) — paginate if needed
- Include Finnhub-sourced data: analyst consensus, price targets, upcoming earnings
Stage 4: Delivery (Beelink / OpenClaw)
scripts/stock_brief.py --deliver
- Read
memory/stock-brief-latest.md - Send via OpenClaw cron (isolated agentTurn) to Matt's DM
- If brief exceeds 4096 chars, split into multiple messages with
(1/2)headers
Full Pipeline (One Command)
scripts/stock_brief.py --run
Runs all four stages sequentially. Exit codes:
- 0 — success
- 1 — partial (data fetched, sentiment/brief fell back to cached or template-based)
- 2 — failure (no data available, notify Matt of failure)
5. Scheduling
OpenClaw cron job:
{
"name": "stock:daily-brief",
"schedule": {
"kind": "cron",
"expr": "30 7 * * 1-5",
"tz": "America/Chicago"
},
"sessionTarget": "isolated",
"payload": {
"kind": "agentTurn",
"message": "Run the daily stock market brief pipeline: execute `scripts/stock_brief.py --run` in the workspace, then send the output to Matt's DM. If the script exits non-zero, report the error."
},
"delivery": {
"mode": "announce"
}
}
Weekdays only (1-5). 7:30 AM CST — 1 hour before market open.
6. Gaming PC Resilience
The Gaming PC may be asleep, gaming, or offline. The pipeline must handle this gracefully:
- Health check: Ping
http://matt-pc.tail864e81.ts.net:11434/api/versionwith 5s timeout - If reachable: Run full local pipeline (deep sentiment + brief composition on GPU)
- If unreachable:
- Log:"Gaming PC offline — using Finnhub sentiment + template brief"
- Finnhub pre-scored sentiment is still available (fetched in Stage 1)
- Compose brief from template (no LLM composition)
- Add footnote to brief:⚠️ Local models unavailable — using Finnhub sentiment + template brief - Never fall back to cloud LLM without explicit user flag (
--deepor config override)
7. Manual / Ad-Hoc Usage
# Run full pipeline now
scripts/stock_brief.py --run
# Just fetch data (no LLM)
scripts/stock_brief.py --collect
# Re-analyze with different model
scripts/stock_brief.py --sentiment --model qwen2.5-coder:14b
# Deep analysis (cloud model, explicit opt-in)
scripts/stock_brief.py --run --deep
# Add a ticker to watchlist
scripts/stock_brief.py --add-watch TSLA --notes "Earnings soon"
# Remove from watchlist
scripts/stock_brief.py --remove-watch TSLA
# Sync positions from Altruist (if enabled)
scripts/stock_brief.py --sync-altruist
# Show current portfolio config
scripts/stock_brief.py --config
8. Technical Stack
| Component | Version/Notes |
|---|---|
| Python | 3.10+ (already on Beelink) |
yfinance |
Latest — quotes, history, news |
pandas-ta |
Technical indicator calculations |
finnhub-python |
Finnhub REST API client (free tier) |
requests |
Ollama API calls to Gaming PC, Altruist OAuth |
feedparser |
Yahoo Finance RSS fallback for headlines |
Authlib or requests-oauthlib |
Altruist OAuth 2.0 flow |
| No database | File-based (JSON cache + Markdown output) |
| No Docker | Runs natively on Beelink |
9. Security Notes
- Finnhub API key — free tier, personal use. Store in
memory/secrets/finnhub-key.txt(chmod 600). Never commit to git. - Altruist OAuth credentials (if granted) — store in
memory/secrets/altruist-token.json(chmod 600). OAuth tokens auto-refresh. - Gaming PC endpoint is Tailscale-only (already zero-trust)
- Portfolio data (cost basis, shares) is sensitive — keep in
memory/(not shared/) - No portfolio data leaves the Tailscale network
- Finnhub calls go through Beelink → public internet (unavoidable for market data)
- Altruist API calls go through Beelink → Altruist servers (OAuth, HTTPS)
10. Future Enhancements (Out of Scope for V1)
- Historical brief archive + trend detection
- Altruist real-time API (account actions, trading)
- Options chain data
- Alert thresholds (RSI > 70, price < SMA, etc.) → push notifications
- Interactive queries ("How's AAPL looking right now?")
- Web dashboard (see Daedalus UX spec)
- Portfolio performance tracking (daily P&L history)
- Multiple portfolio support
- Finnhub pattern recognition + support/resistance integration into briefs
11. Open Questions for Director
- Tickers — What are your owned positions and watchlist? (Or provide Altruist API access for auto-sync)
- ~~Delivery time~~ — Resolved: 7:30 AM CST weekdays
- Cost basis — Do you want P&L tracking? If so, provide cost basis per position (or grant Altruist API access).
- ~~Finnhub~~ — Resolved: Yes. Matt to create free account at finnhub.io and provide API key.
- Alert thresholds — Any specific RSI/price levels you want triggered alerts on?
- Altruist API — Contact Altruist support requesting personal API access to your own account data. If granted, positions auto-sync daily.
Spec by Wadsworth 📋 — routing to Socrates for implementation.