Strategist: local web dashboard with AI council, backtest, journal, download#633
Open
aarshvir wants to merge 14 commits into
Open
Strategist: local web dashboard with AI council, backtest, journal, download#633aarshvir wants to merge 14 commits into
aarshvir wants to merge 14 commits into
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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-uior the Windows autostart) that opens a multi-page web app onlocalhost:8765where you can:FINANCIAL_DATASETS_API_KEYorFMP_API_KEYis setIncludes 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 mappingfinal_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 volbacktest.py— multi-horizon technical backtest + interactive any-date backtestfundamentals_backtest.py— point-in-time historical fundamentals (Financial Datasets first, FMP fallback)fmp_adapter.py— Financial Modeling Prep API adapteragent_runner.py— wraps existingrun_hedge_fundwith stdout captureagent_streamer.py— SSE stream of LangGraph progress per analystcombined.py— orchestrator (shallow vs deep analysis)news.py— yfinance news feed with old + new schema supportmacro.py— VIX, yield curve, S&P regime synthesissettings.py— persistent user settings at~/.strategist/settings.jsonstorage.py— saved snapshots at~/.strategist/saved/<TICKER>/<ts>.jsonwith tags + target-hit detection + journal aggregationwatchlists.py— persistent watchlists at~/.strategist/watchlists.jsonexporter.py— SnapshotReport → Markdownrenderers.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-pullclaude_code.py— LangChain wrapper aroundclaude-agent-sdkfor the subscription fallbackFastAPI surface: 30+ routes including
/,/heatmap,/calendar,/journal,/watchlists,/saved/{T},/settings,/ticker/{T}(with?deep=1and/streamingvariants),/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):
fyi_market_context_agent,fyi_deep_technical_agent,fyi_deep_fundamental_agent) wired into the existing LangGraphsrc/utils/stock_snapshot.py,src/utils/validator.py— companion utilitiessrc/agents/portfolio_manager.py,src/utils/display.py,src/utils/llm.py--effortflag for Claude Code CLI models--no-snapshot,--quietflagsDependencies 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-uiOr for permanent install on Windows:
scripts\install-autostart.bat.Open
http://127.0.0.1:8765/, type any US ticker, hit Analyze. SeeSTRATEGIST.mdin the repo for the full quickstart.Test plan
poetry installresolves cleanly with the two new depspoetry run snapshot-uiopens browser to a working dashboard/saved/{ticker}and/journalwith hit-rate evaluation/saved→ compare → drift charts render as inline SVG/print/{T}auto-fires print dialogBRK.Bresolves toBRK-Bautomatically/api/backtest-at?date=invalidreturns 400 (not 500)Notes for reviewer
src/main.pyandsrc/backtester.pyget a--quietflag and an optional pre-LangGraph snapshot, but the LangGraph pipeline behavior is unchanged.~/.strategist/(override viaSTRATEGIST_DATA_DIR).git initthat directory if you want versioned research history.FINANCIAL_DATASETS_API_KEYif set, falls back to yfinance which is free + keyless.🤖 Generated with Claude Code