# 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 ```json { "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 ```json { "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) ```bash 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) ```bash 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) ```bash 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) ```bash 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) ```bash 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: ```json { "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: 1. **Health check:** Ping `http://matt-pc.tail864e81.ts.net:11434/api/version` with 5s timeout 2. **If reachable:** Run full local pipeline (deep sentiment + brief composition on GPU) 3. **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` 4. **Never fall back to cloud LLM without explicit user flag** (`--deep` or config override) --- ## 7. Manual / Ad-Hoc Usage ```bash # 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 1. **Tickers** — What are your owned positions and watchlist? (Or provide Altruist API access for auto-sync) 2. ~~**Delivery time**~~ — **Resolved: 7:30 AM CST weekdays** 3. **Cost basis** — Do you want P&L tracking? If so, provide cost basis per position (or grant Altruist API access). 4. ~~**Finnhub**~~ — **Resolved: Yes. Matt to create free account at finnhub.io and provide API key.** 5. **Alert thresholds** — Any specific RSI/price levels you want triggered alerts on? 6. **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.*