📄 SPEC.md 13,063 bytes Apr 24, 2026 📋 Raw

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

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

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:

  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

# 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.