Skip to content

Strategist: local web dashboard with AI council, backtest, journal, download#633

Open
aarshvir wants to merge 14 commits into
virattt:mainfrom
aarshvir:main
Open

Strategist: local web dashboard with AI council, backtest, journal, download#633
aarshvir wants to merge 14 commits into
virattt:mainfrom
aarshvir:main

Conversation

@aarshvir

Copy link
Copy Markdown

Summary

This PR adds Strategist — a local, browser-based research dashboard built on top of the existing LangGraph agent pipeline. It's a single command (poetry run snapshot-ui or the Windows autostart) that opens a multi-page web app on localhost:8765 where you can:

  • Type comma-separated tickers and get a full institutional-grade analysis in 5-60s
  • See a snapshot (price, 10 fundamentals, 6 technicals, analyst consensus) with verdicts on every metric
  • Run the existing 14-analyst LangGraph council with live SSE progress per analyst
  • Get a synthesized final verdict: action + 3-point price target + hold period + position size + risk grade
  • View multi-horizon price targets (3M / 6M / 12M / 24M) blending technical / fundamental / analyst lenses
  • Backtest the technical signal at any historical date with hit-rate scoring + alpha vs S&P
  • Backtest historical fundamentals when FINANCIAL_DATASETS_API_KEY or FMP_API_KEY is set
  • See a live macro panel (VIX, 10Y yield, yield curve, S&P regime)
  • View per-ticker news headlines, earnings calendar, universe heatmap
  • Save analyses with tags, build a journal with hit-rate aggregation
  • Pick 2-6 saves of the same ticker and see drift over time as inline SVG charts
  • Download the report as Markdown / PDF (via print) / HTML / JSON
  • Auto-save / auto-run-council / auto-refresh in the background

Includes the Claude Code subscription fallback from #629 — the very first commit on this branch. If that PR lands first, the merge will be clean.

What gets added

New package: src/analysis/ (~3800 lines)

  • snapshot.py — yfinance data pull + assembly (with BRK.B → BRK-B alias resolution)
  • indicators.py — RSI / MACD / SMA / EMA / Bollinger / ATR (pure pandas)
  • verdicts.py — rule-based metric→verdict mapping
  • final_verdict.py — synthesis (snapshot + agents + backtest → action + targets + hold + size)
  • price_targets.py — multi-horizon 3M/6M/12M/24M with bear/bull bands from realized vol
  • backtest.py — multi-horizon technical backtest + interactive any-date backtest
  • fundamentals_backtest.py — point-in-time historical fundamentals (Financial Datasets first, FMP fallback)
  • fmp_adapter.py — Financial Modeling Prep API adapter
  • agent_runner.py — wraps existing run_hedge_fund with stdout capture
  • agent_streamer.py — SSE stream of LangGraph progress per analyst
  • combined.py — orchestrator (shallow vs deep analysis)
  • news.py — yfinance news feed with old + new schema support
  • macro.py — VIX, yield curve, S&P regime synthesis
  • settings.py — persistent user settings at ~/.strategist/settings.json
  • storage.py — saved snapshots at ~/.strategist/saved/<TICKER>/<ts>.json with tags + target-hit detection + journal aggregation
  • watchlists.py — persistent watchlists at ~/.strategist/watchlists.json
  • exporter.py — SnapshotReport → Markdown
  • renderers.py, ui_pages.py, ui_style.py — HTML rendering layer (~3000 lines)
  • web.py — FastAPI app, 30+ routes
  • _series_cache.py — in-memory price/volume cache so interactive backtest doesn't re-pull
  • claude_code.py — LangChain wrapper around claude-agent-sdk for the subscription fallback

FastAPI surface: 30+ routes including /, /heatmap, /calendar, /journal, /watchlists, /saved/{T}, /settings, /ticker/{T} (with ?deep=1 and /streaming variants), /compare, /compare-saves, /compare-saved, /print/{T}, /export/{T}.md|.html|.json, /api/save-ajax, /api/snapshot/{T}, /api/snapshots, /api/backtest-at/{T}, /api/news/{T}, /api/stream-deep/{T} (SSE), /api/settings, /api/watchlists, /healthz.

Windows autostart: scripts/strategist-start.bat, scripts/strategist-stop.bat, scripts/install-autostart.bat, scripts/uninstall-autostart.bat. Installs a Startup folder shortcut so Strategist boots silently on Windows login. Background refresher thread pre-warms watchlist + saved tickers every 6 hours.

Companion changes (not strictly part of Strategist; rode along from working tree):

  • New FYI agent nodes (fyi_market_context_agent, fyi_deep_technical_agent, fyi_deep_fundamental_agent) wired into the existing LangGraph
  • src/utils/stock_snapshot.py, src/utils/validator.py — companion utilities
  • Minor edits to src/agents/portfolio_manager.py, src/utils/display.py, src/utils/llm.py
  • --effort flag for Claude Code CLI models
  • --no-snapshot, --quiet flags

Dependencies added

  • claude-agent-sdk (for the subscription fallback path)
  • yfinance (for the data layer)

Everything else uses the existing stack (FastAPI, langchain, pandas, rich).

How to try it

poetry install
poetry run snapshot-ui

Or for permanent install on Windows: scripts\install-autostart.bat.

Open http://127.0.0.1:8765/, type any US ticker, hit Analyze. See STRATEGIST.md in the repo for the full quickstart.

Test plan

  • poetry install resolves cleanly with the two new deps
  • poetry run snapshot-ui opens browser to a working dashboard
  • Run analysis on NVDA, AAPL, MSFT → results overview table renders with sparklines, sortable, filter chips work
  • Click a row → detail page with all 15 panels (final verdict, multi-horizon, backtest, interactive backtest, news, AI council CTA, snapshot detail, save form, methodology)
  • Click "▶ Run deep analysis" → streaming page shows 14 agent cards filling with ✓ over 30-60s → auto-redirects with full council
  • Flip auto-run toggle directly on the AI Council panel → setting persists → next ticker auto-runs in background
  • Save analysis with tags → appears on /saved/{ticker} and /journal with hit-rate evaluation
  • Multi-select 2+ saves on /saved → compare → drift charts render as inline SVG
  • Download Markdown, HTML, JSON → all open correctly; /print/{T} auto-fires print dialog
  • Invalid ticker returns 404 with friendly page (not 200 empty)
  • BRK.B resolves to BRK-B automatically
  • /api/backtest-at?date=invalid returns 400 (not 500)

Notes for reviewer

  • The web app is purely additivesrc/main.py and src/backtester.py get a --quiet flag and an optional pre-LangGraph snapshot, but the LangGraph pipeline behavior is unchanged.
  • All user data lives at ~/.strategist/ (override via STRATEGIST_DATA_DIR). git init that directory if you want versioned research history.
  • No new auth/secrets surface. Uses existing FINANCIAL_DATASETS_API_KEY if set, falls back to yfinance which is free + keyless.
  • Server is local-only (binds to 127.0.0.1) — not a network service.

🤖 Generated with Claude Code

aarshvir and others added 14 commits May 17, 2026 17:31
If a user has no Anthropic API key but is logged into the Claude Code CLI,
route Anthropic model calls through `claude-agent-sdk` so the run uses
their Claude Code subscription instead of erroring out.

- Add `ChatClaudeCode`, a minimal LangChain `BaseChatModel` that wraps
  `claude_agent_sdk.query()`.
- `get_model()` returns it for the Anthropic branch when no key is found
  (lazy import; if the SDK isn't installed, raise an upgraded error).
- `has_json_mode()` returns False for Anthropic-without-key so call_llm's
  existing JSON-extraction path handles structured output.
- Other providers unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…lyst)

Whenever main.py or the backtester runs, every requested ticker now gets a
full snapshot BEFORE the LangGraph pipeline kicks off — printed to the
console as rich tables and saved to reports/<TICKER>_snapshot_<date>.html.

What every snapshot contains:
- Price performance vs S&P 500 across 1D / 1W / 1M / 6M / 1Y / 3Y
- 10 fundamental metrics each tagged BUY / HOLD / SELL with rationale
  (P/E TTM, Forward P/E, PEG, EV/EBITDA, D/E, ROE 1Y, ROE 3Y avg,
   ROIC proxy, FCF Yield, Revenue Growth 3Y CAGR)
- 6 technical indicators each tagged BUY / HOLD / SELL
  (RSI 14, MACD, Price vs SMA50, Price vs SMA200 + slope, Bollinger
   Bands, Volume vs 50-day avg)
- Analyst consensus panel: total analysts, % Strong Buy / Buy / Hold /
  Sell / Strong Sell, mean/median/high/low price targets, upside %,
  most recent rating action
- Aggregate Fundamental / Technical / Analyst verdicts with confidence
- Composite 0-100 score and overall STRONG BUY / BUY / HOLD / REDUCE /
  SELL verdict, weighted 40% fundamental / 30% technical / 15% analyst /
  15% macro (S&P 200-DMA tilt)
- Rule-based synthesis paragraph (no LLM call required)

Implementation:
- New package src/analysis with: indicators.py (pure-pandas RSI / MACD /
  SMA / EMA / Bollinger / ATR), verdicts.py (rule table), snapshot.py
  (yfinance data fetch + assembly), renderers.py (rich console + single-
  file HTML)
- Wired into src/main.py and src/backtester.py before the LangGraph runs
- New --no-snapshot CLI flag to skip
- New FastAPI endpoints GET /snapshot/{ticker} (JSON) and
  /snapshot/{ticker}/html so the web app inherits it
- Added yfinance to pyproject.toml; no API key required

Also includes companion changes already present on the working tree
(extended FYI agents, validator, codex client, portfolio_manager
formatting, display helpers, claude_code effort param) — they ride
along since they import the same module surface.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Run `poetry run snapshot-ui` (or `python -m src.analysis.web`). A small
FastAPI server starts on http://127.0.0.1:8765 and the default browser
opens. Enter comma-separated tickers, click Analyze, and the full
snapshot (price snapshot vs S&P 500, 10 fundamentals with verdicts,
6 technicals with signals, analyst consensus panel, composite verdict
and synthesis) renders for each ticker on one page.

Implementation:
- New src/analysis/web.py — single-file FastAPI app with inline HTML
  templates; auto-opens the browser; parallel fetch (max 5 concurrent)
- Refactored renderers.py: extracted render_html_body() (returns inner
  HTML without <html>/<head> wrapper) and exposed HTML_STYLE so the
  multi-ticker page can reuse the same styles
- Registered a `snapshot-ui` console script via poetry.scripts

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ompare

Replaced the single-page form UI with a proper local dashboard. Five
routes share a sidebar/topbar shell and a single design system:

  /                     Home — hero search + 8 preset universes + recents
                        + watchlists (localStorage)
  /run?tickers=...      Results overview — 6 KPI cards (buys/holds/sells,
                        avg score, avg 1Y) + sortable, filterable summary
                        table with per-row 90-day sparkline SVG, P/E, ROE,
                        RSI, verdict badge, composite score, sector.
                        Filter chips: All / Buys / Holds / Sells. Click
                        any row to drill in.
  /ticker/{T}?from=...  Single-ticker deep-dive (reuses snapshot body),
                        with prev/next navigation across the result set
                        and a Back link.
  /compare?tickers=...  Metrics matrix — 2-5 tickers side-by-side across
                        verdict, score, returns, fundamentals, analyst
                        targets.
  /watchlists           Saved lists (localStorage, JS-rendered).
  /history              Recent analyses (localStorage).
  /api/snapshot/{T}     JSON
  /api/snapshots?...    Bulk JSON

Design system:
  - Inter + JetBrains Mono via Google Fonts
  - Dark navy palette w/ gradient backdrop
  - Consistent BUY / HOLD / SELL / REDUCE badges across all pages
  - Loading overlay on form submit (CSS spinner + ticker echo)
  - '/' keyboard shortcut to focus search; Esc to dismiss overlay
  - Sticky table header, column sort, search + filter chips

Other:
  - SnapshotReport now carries `price_sparkline` (last 90 closes).
    Rendered inline as an SVG path + area fill per row in the overview.
  - In-memory snapshot cache (5-min TTL) so navigating between
    overview/detail/compare for the same set doesn't refetch.
  - All snapshots fetch in parallel (max 5 yfinance calls concurrent).

Run: `poetry run snapshot-ui` → opens http://127.0.0.1:8765/

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This replaces the shallow snapshot-only dashboard with a real research
console. Each ticker now carries four layers, all surfaced in the HTML:

1. yfinance snapshot (5s) — unchanged
2. Multi-horizon technical backtest (1M / 3M / 6M / 1Y) with hit-rate
   tracking + alpha vs S&P 500. Shows what the model would have
   recommended at each historical date, and how the call played out.
3. AI Investor Council (opt-in via ?deep=1, ~30-60s) — wraps the
   LangGraph pipeline (Buffett, Munger, Lynch, Druckenmiller, Marks,
   Klarman, Fisher, Pabrai, Taleb, Cathie Wood, Damodaran,
   Jhunjhunwala + Risk Manager + Portfolio Manager). Stdout from
   agents is fully captured so the web server stays clean.
4. Final Verdict synthesis (always) — combines snapshot composite
   (55%), AI council (45% when present), backtest hit rate
   (conviction modifier), into a single action + 3-point price
   target + hold period + position size + risk grade.

New files:
  src/analysis/backtest.py      — multi-horizon backtest engine
  src/analysis/agent_runner.py  — LangGraph wrapper with stdout capture
  src/analysis/final_verdict.py — rule-based synthesizer + transparent
                                  methodology explainer
  src/analysis/combined.py      — orchestrator (shallow + deep paths)

UI changes:
  - Ticker detail page is now: Final Verdict banner → How we got there
    (rationale + catalysts + risks) → Backtest panel with hits/misses
    table → AI Council panel (CTA when not run; full table when run)
    → Portfolio Manager decision card → Snapshot detail (existing) →
    Methodology (collapsible <details>)
  - Overview table gains a Hit Rate column derived from each ticker's
    backtest. Verdict / Score now come from FinalRecommendation, not
    just the snapshot.
  - Every section ships with a one-line "what this means" caption so
    the report stands on its own without you reading the code.
  - Page chrome: action bar gets "Run deep analysis" CTA when the AI
    council hasn't been triggered yet, plus a jump-link to the
    Backtest panel.

CLI / web hygiene:
  - `python src/main.py --quiet` skips the rich-console snapshot — HTML
    is still written to ./reports/. The recommended UX is now
    `poetry run snapshot-ui` which routes everything through the web.
  - Web cache (5-min TTL) now upgrades in place: a shallow result gets
    its AI council attached on first ?deep=1 visit, no re-fetch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two adds:

1. **Multi-horizon price target model** (src/analysis/price_targets.py)

   For each horizon (3M / 6M / 12M / 24M) we produce three independent
   estimates and weight them:
     - Technical: 6-month annualised momentum projected forward with
       horizon-dependent decay (0.65 ≤6M → 0.30 24M), clamped to ±50%/yr
     - Fundamental: projected EPS × target P/E, growth clamped to
       [-30%, +60%], sanity-clamped to ±60%/yr from spot
     - Analyst: scale mean 12M target linearly (<12M) or compound at the
       same implied growth rate (>12M)
   Weights: 3M (Tech 55 / Fund 25 / Analyst 20) → 24M (Tech 10 / Fund 55 /
   Analyst 35).
   Bear / bull cases are 1σ-wide bands derived from realised daily-log-
   return volatility, scaled by √(months/12) so the envelope widens with
   time. Confidence per horizon scales with how tightly the three lenses
   cluster.

   Rendered as a fully tabular panel on the detail page with: Technical,
   Fundamental, Analyst, Combined, Bear, Bull, Upside%, Downside%,
   Confidence, and a per-row visual bar showing bear ⇢ bull range with
   current-price marker and combined-target dot.

2. **Save & compare snapshots** (src/analysis/storage.py)

   - POST /api/save/{ticker} persists the full SnapshotReport to
     ~/.strategist/saved/<TICKER>/<YYYY-MM-DD_HH-MM-SS>.json (location
     overridable via STRATEGIST_SAVED_DIR env var) with an optional
     user note.
   - GET /saved lists all saves across tickers; GET /saved/{ticker}
     filters to one. Card grid with verdict badge, score, price at save,
     note, and a 'Compare with now' CTA per saved item.
   - GET /compare-saved/{ticker}/{timestamp} renders a side-by-side:
       * Realized-since-save card with price-then, price-now, return,
         and a 'the saved call paid off / missed' verdict check
       * Headline diff: verdict, composite score, current price, 12M
         target, hold period — each with a delta column
       * 10 Fundamentals diff table — saved value/verdict vs current,
         flagged when the verdict flipped
       * 6 Technicals diff table — saved signal vs current, flagged when
         the signal flipped

UI additions:
  - 'Saved' added to the sidebar nav (between Home and Watchlists)
  - 'N saved' pill in the detail-page topbar that links to the saved list
  - 'Save this analysis' panel at the bottom of every detail page with an
    inline note input and the ★ Save button
  - Topbar quick-jump links: 🎯 Targets · ⏪ Backtest · 💾 Save

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…damentals

Four features delivered in the order requested:

1. **SSE streaming for the AI investor council**
   - New src/analysis/agent_streamer.py hooks into the existing
     `progress.register_handler` mechanism so every agent status
     update is pushed to an asyncio queue from the worker thread.
   - GET /api/stream-deep/{ticker} yields the queue as Server-Sent
     Events (`type: start|agent_update|agent_done|agent_error|done|error`).
   - GET /ticker/{T}/streaming renders a live progress page: a grid
     of 19 council cards (Buffett, Munger, Lynch, Druckenmiller,
     Marks, Klarman, Fisher, Pabrai, Taleb, Wood, Damodaran,
     Jhunjhunwala, Valuation, Sentiment, Fundamentals, Technicals,
     Risk Manager, PM) that flip from ⋯ Pending → ⋯ Working →
     ✓ Done as events arrive. Progress bar + counter + elapsed timer.
   - Auto-redirects to /ticker/{T}?deep=1 (cached agent result) when
     the council finishes, so the full report renders instantly.
   - "▶ Run deep analysis" button now points to the streaming page.

3. **Live macro context panel**
   - New src/analysis/macro.py pulls VIX, ^TNX (10Y), ^IRX (13W as
     2Y proxy), ^GSPC + 200-day MA, and CNN Fear & Greed.
   - 5-min cache; degrades gracefully if any series fails.
   - Synthesises a regime label (Risk-on / Mixed / Risk-off) with
     colour-coded emoji.
   - Renders as: (a) a macro tape strip in the page topbar pulse slot
     on every page; (b) a full 5-card panel at the top of the Home
     page above the search hero.

4. **Interactive backtest with date picker**
   - New backtest_at_date() in src/analysis/backtest.py — runs the
     6 technical signals at any user-chosen historical date.
   - New tiny in-memory series cache (_series_cache.py) so the
     endpoint reuses the price/volume/SPX series already pulled by
     the most recent /api/snapshot call — no fresh yfinance round-trip.
   - GET /api/backtest-at/{ticker}?date=YYYY-MM-DD returns
     {label, verdict, confidence, price_then, price_now, realized_return,
      alpha, correct, indicators[], fundamentals}.
   - New "📅 Interactive backtest" panel on the ticker detail page:
     <input type="date"> + "Run backtest" button + AJAX. Results
     render inline as a verdict banner + indicators table + (when
     available) the point-in-time fundamentals table.

2. **Historical FUNDAMENTAL backtest** (was previously technical-only)
   - New src/analysis/fundamentals_backtest.py calls the existing
     project tool `src.tools.api.get_financial_metrics(ticker,
     end_date, period='ttm', limit=1)` — Financial Datasets returns
     point-in-time TTM metrics for the most recent period ≤ date.
   - Grades them through the same verdict rules used in the live
     snapshot (P/E, EV/EBITDA, D/E, ROE, ROIC, FCF Yield, Revenue
     Growth) and produces a fundamental verdict for the historical
     date alongside the technical one.
   - Requires FINANCIAL_DATASETS_API_KEY in env. Web app now calls
     `load_dotenv()` at import time so the same .env that powers
     `python src/main.py` is picked up by `snapshot-ui`.
   - When the key is absent, the API still works (technical-only)
     and the JS shows a clear warning instead of breaking.
   - Verified live: NVDA 2025-11-19 returned P/E 45.6 SELL, ROE
     103.8% BUY, D/E 0.36 BUY, FCF Yield 1.7% SELL — the kind of
     mixed read that's impossible with yfinance's current-only
     fundamentals.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ve drift, settings, FMP fallback

Everything you do now survives reboots, browser changes, and machine moves.
All data lives under ~/.strategist/ — one JSON file per save, plus three
top-level files for settings/watchlists. The whole directory is plain
JSON; `git init` it if you want versioned history.

Layout:
  ~/.strategist/
    settings.json                          new — UI preferences
    watchlists.json                        new — moved from localStorage
    saved/<TICKER>/<ts>.json               existing — now with tags + source

New files:
  src/analysis/settings.py        load/save Settings dataclass
  src/analysis/watchlists.py      JSON-backed CRUD for watchlists
  src/analysis/fmp_adapter.py     FMP fundamentals fallback when
                                  FINANCIAL_DATASETS_API_KEY is absent

Storage upgrades (src/analysis/storage.py):
  - save_snapshot() takes `tags` and `source` (manual / auto)
  - already_saved_today(ticker, source) for idempotent auto-save
  - update_tags() rewrites tags on an existing save
  - list_saved(tag=...) filters by tag
  - list_all_tags() returns tag counts for the filter strip
  - evaluate_target_hits() computes whether current price has reached
    the saved 12M target, with progress %
  - journal_summary() aggregates hit rate, avg return, avg hold,
    best/worst performers across the full journal

Auto-save (web.py):
  - When Settings.auto_save_enabled = true, every /ticker/{T} view drops
    a snapshot to disk if we haven't already auto-saved that ticker today.
  - Builds your journal in the background as you research, no clicks.

New UI surfaces:
  - /settings — toggle auto-save, default tag, default analyst panel;
                shows status of all configured data sources (yfinance,
                Financial Datasets, FMP, Polygon)
  - /journal — saved-verdicts dashboard. KPI cards (total, buys, holds,
                sells, overall hit rate), hit-rate-by-verdict table, best
                & worst performer tables, recent activity. Tag filter
                chips across the top.
  - /compare-saves?ticker=X&ts=ts1,ts2,... — multi-save drift view with
                inline SVG line charts: composite score over time,
                price evolution over time, plus mini detail cards per
                save. No client-side chart library.
  - /watchlists — fully server-side now. Create/delete via POST forms;
                JS reads /api/watchlists on load. Survives browser
                resets, profile switches, anything.
  - Home page — Recent + Watchlists slots now server-rendered from
                the persistent stores instead of localStorage.

Saved-list upgrade:
  - Tag filter chip row above the cards
  - Each card shows: tag pills, target-progress bar (🎯 Hit! when
    current price reaches the saved 12M mid target), realized return,
    source (manual/auto), and a checkbox for multi-select compare
  - "⇆ Compare selected" button activates when 2+ checkboxes ticked

Save form gains:
  - Tags field (comma-separated)
  - Inline hint linking to /settings for auto-save

FMP fallback for historical fundamentals:
  - When FINANCIAL_DATASETS_API_KEY is absent, fundamentals_backtest
    transparently falls back to FMP if FMP_API_KEY is set.
  - Settings page surfaces which data source is configured/available.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ifications

Two UX issues fixed this turn.

1. **AI Investor Council can now run automatically on every ticker view.**
   - New setting `Settings.auto_run_council` (default off).
   - When toggled on in `/settings`, every `/ticker/{T}` view that lacks
     cached agent results silently opens an EventSource to `/api/stream-deep`
     in the background.
   - Yellow banner at top of the detail page shows live progress
     ("X of 19 done · last: Druckenmiller"). User keeps reading the snapshot
     while the council runs.
   - When the council finishes (≈30-60s), a green "✓ AI Council complete"
     toast fires and the page auto-refreshes with `?deep=1` to render the
     full report.
   - "Watch live →" link in the banner takes the user to the existing
     full-screen streaming page if they prefer.
   - New `GET /api/settings` (JSON) so client-side scripts can read the
     setting on page load.

2. **Save UX overhauled.**
   - Prominent **★ Save** button now sits in the topbar of every detail
     page (was buried at the bottom).
   - Click opens a modal: note + tags inputs with quick-tag chips
     (research / watchlist / decision / position-entered).
   - Submit POSTs to new `/api/save-ajax/{ticker}` which returns JSON
     `{ok, timestamp, tags, saved_at, has_agents, council_size}` instead
     of redirecting away.
   - Success → green toast with "View saved →" and "Journal →" links.
     User stays on the detail page.
   - Keyboard shortcut `Ctrl/Cmd+S` opens the quick-save modal.
     `Esc` closes it.

Supporting work:
- Generic toast system: `window.showToast({kind, title, message, actions, duration})`
  with success / error / info variants. Rendered into a fixed-position
  stack in the bottom-right of every page. Used by Quick Save and the
  auto-council completion notification.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ran a deep validation pass on every route and edge case. Three real bugs
found and fixed, four missing features built, two nav items added.

Bugs fixed (all P0):
1. /api/backtest-at returned 500 on invalid date (e.g. ?date=invalid).
   Now wraps the pd.Timestamp parse in try/except and returns a 400 with
   a helpful 'use YYYY-MM-DD format' message.
2. /ticker/{INVALID_TICKER} returned 200 with an empty all-N/A page
   (yfinance silently returns nothing for garbage symbols). Now detects
   the empty report case (`_is_empty_report()` checks current_price,
   analyst counts, and whether any fundamental metric has a non-None
   value) and renders a friendly 'ticker not found' page with a 404.
3. /ticker/BRK.B returned 404 because yfinance uses '-' (not '.') for
   class-share suffixes. New `_resolve_yfinance_symbol()` retries with
   the hyphen variant — so BRK.B, BF.B, RDS.A all now resolve correctly.

Missing features built:
- /heatmap?tickers=... — universe heatmap (sector × verdict grid).
  Sized by market cap (log scale), colored by verdict, hover for context,
  click to drill in. Empty state shows preset universe chips.
- /calendar — earnings calendar aggregating across all saves + watchlists.
  Grouped by week bucket (Imminent / This week / Next week / Within 30d /
  Within 90d / Later / Past), showing days-to-earnings and source.
- /api/news/{ticker} + News panel on the ticker detail page — pulled from
  yfinance.news with both old and new (Aug 2024+) schema support,
  normalised to {title, link, publisher, summary, published}, cached
  10 min. Up to 8 headlines per ticker on the detail page.
- ticker_not_found_page() — friendly 404 with quick-start ticker chips.

Nav consistency:
- Sidebar now has 7 items: Home / Heatmap / Earnings / Saved / Journal /
  Watchlists / Settings (was 5).
- Validated: every page renders the full nav, all hrefs work,
  HTML tags balanced across all 9 reference pages.

Final route audit results:
  33/33 pass · 0 fail
  - 19 HTML pages (200)
  - 6 JSON APIs (200)
  - 8 4xx error paths returning the correct status codes

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three things shipped:

1. **Windows autostart**
   - scripts/install-autostart.bat — creates a shortcut in
     %APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup that points
     at scripts/strategist-start.bat. Idempotent.
   - scripts/uninstall-autostart.bat — removes it.
   - scripts/strategist-start.bat — silent background launcher:
       * detects an already-running instance (curl /healthz with --connect-timeout 1)
         and no-ops with a friendly message
       * detaches via `start /MIN cmd /c` so the console window closes
       * logs to %USERPROFILE%\.strategist\logs\strategist.log
       * --open flag to also open the browser
       * --foreground flag for debug mode (visible console)
   - scripts/strategist-stop.bat — kills whatever's bound to TCP/8765
     (PowerShell Get-NetTCPConnection + Stop-Process).

   Smoke-tested end-to-end:
     * start → /healthz responds in 3s
     * second start → "already running" no-op
     * install-autostart → 1832-byte .lnk created in Startup folder
     * uninstall-autostart → shortcut removed
     * stop → server unreachable

2. **Background refresher thread**
   New `_start_background_refresher()` spins up a daemon thread that
   every 6 hours iterates over every ticker in your saves + watchlists
   and warms `_fetch_many()` in parallel. Result: when you wake up your
   laptop the next morning, all your tickers load instantly because
   they're already in the in-memory cache.

   - Initial delay 30s so we don't hammer at boot.
   - Crash-safe — single-ticker failures are swallowed.
   - Opt out via STRATEGIST_NO_REFRESHER=1 in env.

3. **/healthz endpoint + FastAPI lifespan migration**
   - GET /healthz returns {ok, pid, cache_size, data_dir} — used by the
     "already running" check in start-strategist.bat and any external
     monitor.
   - Migrated from the deprecated `@app.on_event("startup")` pattern to
     FastAPI's native lifespan context manager. No more deprecation
     warnings in the log.

Plus STRATEGIST.md — single-page quickstart covering the three run modes
(manual, autostart, foreground-debug) and what happens in the background.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a 4-format download menu on the ticker detail page, all served from
the same in-memory snapshot cache so they're instant once the page loads.

New endpoints:
  GET /export/{ticker}.md     — clean Markdown, downloadable
  GET /export/{ticker}.html   — self-contained single-file HTML
  GET /export/{ticker}.json   — full SnapshotReport dataclass tree
  GET /print/{ticker}         — print-optimized page that auto-fires
                                window.print() on load → user picks
                                "Save as PDF" in the browser dialog

Markdown export (src/analysis/exporter.py):
  Mirrors the detail-page sections in their full depth — final verdict
  banner with action / score / confidence / price targets / hold period /
  position size / risk grade; rationale + key catalysts/risks; price
  performance vs S&P table; 10 fundamental metrics with verdicts; 6
  technical indicators with signals; analyst consensus panel; multi-
  horizon price targets table (3M/6M/12M/24M); backtest hit-rate table
  with per-horizon hits/misses; AI investor council table with per-
  analyst signal + reasoning; Portfolio Manager decision block;
  synthesis paragraph; methodology footer. Pure stdlib + the
  SnapshotReport dataclass, no external deps.

Print view (/print/{ticker}):
  - Print-friendly CSS (@media print): white background, black text,
    grayed borders, hidden no-print elements, page-break rules to keep
    panels and table rows together
  - @page sizing: A4 with 18mm/16mm margins
  - "Save as PDF" instruction banner at top (hidden when printing)
  - Auto window.print() ~600ms after load so the user lands directly
    in the browser print dialog with "Save as PDF" pre-selectable
  - Zero Python dependencies (no weasyprint/playwright)

UI:
  Topbar Download ▾ dropdown on every ticker detail page with all
  four options. Replaces the inline { } JSON button. Click outside
  to dismiss. Keyboard-accessible.

Error handling:
  All four formats return 404 (with detail) for unknown tickers,
  consistent with the rest of the platform.

Smoke-tested live:
  /export/NVDA.md  → 5128 bytes, text/markdown
  /export/NVDA.html → 9436 bytes, self-contained
  /export/NVDA.json → 17664 bytes, valid JSON
  /print/NVDA      → 30846 bytes, has @media print + window.print() + @page

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The auto-run-council setting lived only in /settings — easy to miss.
Now it sits inline on every ticker detail page, right next to the
manual 'Run now' button. The CTA panel is split into two cards:

  ┌─ ▶ Run manually ──────────────────────┐    ┌─ 🔄 Auto-run on every ticker view ─┐
  │ One-off: kick off the council for     │    │ ON  — runs in background on every  │
  │ this ticker only. Live progress, ~30s │    │ view. Page refreshes when done.    │
  │                              [Run now]│    │                            [toggle]│
  └───────────────────────────────────────┘    └────────────────────────────────────┘

Clicking the toggle:
  - POSTs to /api/settings preserving the other settings
  - Shows a toast confirmation ('✓ Auto-run enabled' / '○ disabled')
  - If now ON, navigates to the streaming URL so the user sees it work
    right away (without waiting for the next manual click)
  - On failure, snaps back to the previous state

UI:
  - Polished iOS-style toggle (44×24, animated thumb, buy-green when on)
  - Subtle subtitle text that updates dynamically based on current state
  - Two-card grid layout, stacks on mobile (<720px)
  - Initial state loaded async from /api/settings on page load

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two real fixes after smoke-testing GPT-5.5 via the Codex CLI subscription:

1. **Switch transport from raw OpenAI API to `codex exec` subprocess.**
   The previous implementation passed the OAuth token from ~/.codex/auth.json
   as a Bearer token to api.openai.com/v1/chat/completions. That hits the
   API billing wall: ChatGPT subscriptions return 429 insufficient_quota.
   Now we shell out to `codex exec -m <model> -c model_reasoning_effort=...
   -s read-only -o <file> <prompt>` and read the last message back from
   the output file. Same pattern as `claude-agent-sdk` for Claude. Uses
   the ChatGPT subscription directly with zero extra billing.

2. **Fix prompt framing so Codex doesn't ask for clarification.**
   Codex CLI is a coding agent — when handed a generic prompt, it treats
   the first paragraph as task framing and waits for a concrete action.
   We tested three approaches:
     a) `[SYSTEM]/[USER]` markers → Codex ignores the question, asks
        "What would you like me to work on in this repo?"
     b) Concat with blank line, system-first → Codex reads it as priming
        and asks "Send the ticker and specific question."
     c) **User-first**, then append system as "Answer style: …" → Codex
        answers the actual question with full institutional analysis.
   We ship (c).

3. **Run from a temp directory.**
   Codex's exec mode treats `cwd` as a project context. Running from the
   AI hedge fund repo made Codex offer to "work on the repo" instead of
   answering the question. Subprocess now runs in a fresh
   `tempfile.TemporaryDirectory()` so Codex behaves as a general LLM.

Also:
- Default `effort="xhigh"` (was "high") — matches the platform's bias
  toward deepest available reasoning for analysis use.
- Validate effort against {minimal, low, medium, high, xhigh, max};
  unknown values warn and fall back to "high".
- 10-minute subprocess timeout (extra-high reasoning can be slow).
- Clean errors if codex CLI is not on PATH or auth.json missing.

Smoke-tested live: NVDA vs AAPL 12M-upside comparison via gpt-5.5/xhigh
returned in 50s with real numbers (NVDA +23.6% vs AAPL +2.0%), growth
comparison, risk analysis, and a clear verdict — institutional-grade.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant