# Investment Analyst Agent Spec
Status: Draft Feature owner: TBD Last updated: 2026-05-31
# Goal
Build a separate-process investment analyst agent that converts trusted upstream AI research into a final investment thesis, corresponding portfolio decision, and human-approved broker execution plan.
For v1, the primary upstream research source is the Capitalist Exploits McIntosh/MacIntyre newsletter folder at analyses/mcintosh/. The source model must allow additional newsletter folders later, but v1 implementation should stay focused on McIntosh. The agent must read numbered McIntosh issues, bias toward the most recent issue by largest issue number, understand the continuing thesis across recent issues, then perform deeper fundamental and technical analysis on candidate securities before producing a portfolio decision for a starting budget of USD 10,000.
The intended product is a self-running AI fund workflow. The investment analyst agent makes the final portfolio decision and, after explicit human approval, submits the approved trades through its own Interactive Brokers account. The human user reviews the decision record and approves or rejects execution; the system must not place trades, modify accounts, or imply guaranteed outcomes without that approval gate.
The strategy is investment-oriented, not trading-oriented. Assets selected for purchase must be suitable for a minimum expected holding period of at least 6 months. The agent may position cash in preparation for major market moves, but should not decide on short-term trading, high-turnover tactics, or entry/exit activity driven only by technical signals.
# Non-Goals
- No broker order placement or broker account modification before explicit human
approval. The agent may submit trades only after the user approves the exact proposed portfolio/order set.
- No direct browser-side calls to Fiscal.ai, Interactive Brokers, Questrade,
Anthropic, or any other financial/AI API.
- No high-frequency, day-trading, options-trading, margin, or short-selling
workflows in v1.
- No fully autonomous scheduled portfolio refresh in v1 unless explicitly
enabled later. Runs should be manually triggered or explicitly invoked by an authenticated service endpoint.
- No execution inside the Flask request lifecycle in v1. The Flask app should
read the latest completed decision and display it, but the analyst run itself must execute in a separate process/service.
- No storage of raw OAuth tokens, Fiscal.ai API keys, Anthropic API keys,
Interactive Brokers credentials, raw filings, full upstream API responses, or full model transcripts.
- No attempt to support every upstream analyst source. Use a folder-based source
abstraction, but implement McIntosh only for v1.
# Operating Model
The feature should run as a separate process, invoked from the command line:
uv run investobot-analyst run --source mcintosh --budget-usd 10000
The production deployment target is Fly.io. The analyst should run as a second service/process in the same Fly app, on its own machine, separate from the Flask web server machine. The Flask web app should display the latest completed decision, but the analyst run itself should not execute inside the web server process.
In addition to the CLI, expose an authenticated service endpoint for triggering an analysis run:
POST /run-analysis
Content-Type: application/json
Example request body:
{
"source": "mcintosh",
"budget_usd": 10000,
"issue_numbers": [328, 327, 326]
}
The endpoint should create a PortfolioStrategy row with status analysis_in_progress, enqueue or start a bounded run, and return a run identifier. It must not block an HTTP request until a full analysis completes.
# Web App Integration
The main Flask app should read the latest analyst strategy from the database and show it as the current agent instruction set for human review and approval.
The web app must make the portfolio strategy status visually obvious:
- analysis in progress,
- strategy submitted and awaiting human approval,
- approved for broker execution,
- broker execution in progress,
- implemented.
The UI should show enough metadata for the user to understand what is happening:
- current status,
- active/latest run id,
- started/submitted/approved/executed/implemented timestamps,
- strategy title and summary,
- proposed broker execution instructions,
- holdings to buy,
- residual cash,
- source reports used,
- links to the generated report artifacts.
The preferred presentation is a rich, interactive document embedded in the main web app. It should contain:
- written summary,
- final human-approval and broker-execution instructions,
- holdings and rejected-candidate tables,
- methodology and score tables,
- interactive price, valuation, and exposure charts,
- source references to every
analyses/report used.
Use a trusted, conservative data-science stack. For dataframe work, use Polars. Do not add pandas for v1 unless a specific library integration requires it. Additional visualization or notebook dependencies require explicit approval, exact pins, a lockfile update, and the full check suite.
Reporting decision for v1: use marimo and export the strategy report as WebAssembly HTML. The report should be a rich document with charts, tables, and interactive controls, not a Markdown-only artifact.
Marimo notes:
- Store the report notebook as a Python file generated from structured strategy
artifacts.
- Export with
marimo export html-wasmin read-only run mode. - Upload the exported HTML and adjacent
assets/directory to Tigris object
storage, then serve them through the Flask web app or short-lived signed URLs.
- Do not rely on
file://loading; WebAssembly HTML exports must be served over
HTTP.
- Validate that required plotting/data packages work in the WebAssembly export
before depending on them for the primary report.
Streamlit is not the v1 reporting target. It can remain a future alternative if marimo WebAssembly export proves incompatible with required charts or private deployment constraints.
Proposed package layout:
src/investobot/analyst/
__init__.py
cli.py
config.py
mcintosh.py
fiscal_client.py
broker.py
ibkr_client.py
eligibility.py
fundamentals.py
technicals.py
filings.py
portfolio.py
report.py
service.py
Proposed artifact object-key layout:
analyst-runs/<run-id>/
report.py
report.html
assets/
portfolio.json
candidates.json
methodology.json
sources.json
decision.json
Fly.io machines should be treated as ephemeral. The feature must not depend on local persistent volumes for analyst artifacts. Local disk may be used only as a temporary workspace during a run; durable artifacts must be uploaded to Tigris object storage before a strategy is submitted.
# Artifact Storage
Use Tigris object storage for generated analyst artifacts in Fly.io.
Tigris is S3-compatible object storage built into Fly.io infrastructure. A Fly Tigris bucket can be created with fly storage create. Fly may expose AWS-compatible secret names by default, but Investobot should normalize those into Tigris-specific app configuration so the application interface does not reference AWS.
Required behavior:
- Store all report and machine-readable artifacts under an object prefix such as
analyst-runs/<run-id>/.
- Upload
report.html,report.py, WebAssembly exportassets/, and JSON
artifacts before moving a strategy to strategy_submitted_awaiting_human_approval.
- Persist object keys, not local filesystem paths, in the database.
- Treat the bucket as private by default because reports contain financial
strategy and execution instructions.
- Prefer serving artifacts through authenticated Flask routes that fetch from
Tigris, or generate short-lived signed URLs if direct browser access is needed.
- Do not make a Tigris bucket public unless explicitly approved.
- Do not store secrets, raw API payloads, raw filing bodies, full model
transcripts, or account identifiers in artifacts.
- If artifact upload fails, keep the strategy out of
strategy_submitted_awaiting_human_approval and surface a sanitized failure state.
Proposed environment values:
TIGRIS_BUCKET_NAMETIGRIS_ENDPOINT_URLTIGRIS_ACCESS_KEY_IDTIGRIS_SECRET_ACCESS_KEYTIGRIS_REGION
Deployment note: if Fly provisions AWS-compatible variable names for Tigris, map them to the TIGRIS_* names in Fly secrets or release configuration. Do not read AWS-named variables directly in application code.
Dependency note: Python S3 client support will require explicit dependency approval before implementation. Prefer a narrow existing-compatible option, pin it exactly, update uv.lock, and run the full check suite.
# Database Model
The analyst workflow needs durable database objects so the web app can show the current state even when the analyst service is not running. Use the existing Piccolo/SQLite setup.
# PortfolioStrategy
Tracks the overarching analysis and final strategy recommendation.
Proposed fields:
id: primary key.run_id: stable identifier matching generated artifacts.source: upstream analyst source,mcintoshfor v1.broker: execution broker,interactive_brokersfor v1 broker execution.status: enum-like string:analysis_in_progressstrategy_submitted_awaiting_human_approvalapproved_for_broker_executionbroker_execution_in_progressimplementedcancelledtitle: short strategy name for the UI.summary: concise strategy summary.budget_usd: starting budget used for sizing.residual_cash_usd: cash left after whole-share sizing.thesis: overarching analysis and strategy recommendation text.execution_instructions: proposed Interactive Brokers order instructions for
human approval and later agent submission.
artifact_bucket: Tigris bucket name.artifact_prefix: object prefix, such asanalyst-runs/<run-id>/.report_object_key: object key for generated marimo WebAssembly HTML report.report_notebook_object_key: object key for generated marimo Python notebook
source.
portfolio_json_object_key: object key for generated final holdings artifact.sources_json_object_key: object key for source-reference artifact.decision_json_object_key: object key for latest decision artifact.source_issue_numbers: JSON array or serialized list of source issue numbers
used. For McIntosh, these are McIntosh issue numbers.
submitted_at: timestamp set when analysis succeeds and human approval is
required.
approved_at: timestamp set when the human approves the exact proposed
portfolio/order set for broker execution.
broker_execution_started_at: timestamp set when approved order submission
begins.
broker_execution_completed_at: timestamp set when broker execution finishes
or all submitted orders reach a terminal tracked state.
implemented_at: timestamp set when execution is complete and the strategy is
marked implemented.
cancelled_at: timestamp set when a strategy is cancelled or superseded.created_at: timestamp.updated_at: timestamp.error_message: sanitized failure summary, nullable.
Only one strategy should normally be active at a time. If a new analysis starts while a prior strategy is awaiting approval, approved for execution, or in broker execution, the prior strategy must first be set to cancelled unless it has already submitted live broker orders. Once live orders exist, supersession must use an explicit reconciliation flow rather than silently cancelling the strategy. The UI must make cancellation/supersession visible so the human does not approve or execute stale instructions.
# PortfolioStrategyHolding
Tracks the final holdings attached to a strategy in a UI-friendly form.
Proposed fields:
id: primary key.strategy: foreign key toPortfolioStrategy.ticker: ticker symbol.exchange: exchange code.name: company or fund name.action:buyfor v1.quantity: whole-share quantity.latest_price_usd: price used for sizing.allocation_percent: target allocation.allocation_usd: target dollar allocation.thesis_role: why the asset is in the strategy.minimum_holding_months: minimum intended holding period, at least 6.target_review_date: date for reassessment.fundamental_score: nullable numeric score.technical_score: nullable numeric score.combined_score: nullable numeric score.risks: concise risk summary.invalidation_triggers: concise thesis-break conditions.
# PortfolioStrategySource
Tracks source analysis references used by a strategy.
Proposed fields:
id: primary key.strategy: foreign key toPortfolioStrategy.source_name:mcintoshfor v1.issue_number: parsed issue number.file_path: local path such asanalyses/mcintosh/issue-328.md.is_primary: whether this was the latest/highest-priority source.
# Status Transitions
Allowed transitions:
analysis_in_progress
-> strategy_submitted_awaiting_human_approval
-> approved_for_broker_execution
-> broker_execution_in_progress
-> implemented
strategy_submitted_awaiting_human_approval
-> cancelled
approved_for_broker_execution
-> cancelled
broker_execution_in_progress
-> implemented
analysis_in_progress
-> cancelled
Failure behavior:
- If analysis fails before submission, persist a sanitized failure state or
failure message associated with the in-progress strategy.
- Do not move to
strategy_submitted_awaiting_human_approvalunless a complete
strategy, holdings, proposed execution instructions, and source references were generated.
- Do not move to
approved_for_broker_executionunless the human explicitly
approves the exact proposed portfolio/order set in the web app.
- Do not submit orders to Interactive Brokers unless the strategy is already in
approved_for_broker_execution and the order set still matches the approved decision record.
- The
implementedtransition should occur only after broker execution is
complete and order/account state has been reconciled.
- The
cancelledtransition should be triggered only by explicit human action
or a clearly recorded supersession flow before a replacement analysis begins.
# Inputs
# Newsletter Analyses
Newsletter inputs live under source-specific folders in analyses/, for example:
analyses/mcintosh/for Capitalist Exploits McIntosh/MacIntyre issues.
v1 implements McIntosh ingestion only, but the source-discovery shape should not hard-code an assumption that McIntosh will be the only newsletter forever. Future sources should be added as explicit source adapters with their own parsers, naming rules, and source-reference metadata.
# McIntosh Analyses
Input path: analyses/mcintosh/
Issue files are named like issue-328.md. The agent must:
- Discover all issue files with a strict filename parser.
- Sort by parsed issue number descending.
- Accept an optional explicit issue-number list for targeted strategy updates.
- Treat the largest selected issue number as the primary source.
- If no explicit issue list is provided, read enough recent history to identify
recurring themes and changes in conviction. For v1, default to the latest 3 issues.
- Extract:
- dominant macro thesis,
- favored sectors,
- avoided sectors,
- named companies and tickers,
- suggested position-sizing constraints,
- time horizon,
- stated risks and invalidation conditions.
The agent should effectively trust McIntosh's direction, but not blindly copy the final security list. It must validate purchaseability, account eligibility, valuation, balance sheet quality, liquidity, and current technical setup.
For traceability, every McIntosh issue used in the run must be recorded with its issue number, file path, and role in the final decision. The final report should include references to these source reports.
Current observed v1 trend from issues 326-328:
- Strong recurring bullish thesis on energy, especially offshore oil and gas
equipment/services, drilling support, LNG infrastructure, and broad hard asset/commodity exposure.
- Latest issue 328 adds stronger emphasis on structural energy security,
non-Middle-East production, oil and gas services capacity constraints, LNG disruption, and large-cap energy anchors.
- Recurring avoid/hedge theme against Nasdaq, AI/Mag 7, long-duration growth,
and crowded financial assets.
# Starting Budget
Default budget: USD 10,000.
The budget is a hard sizing input. The portfolio engine must generate whole share quantities where possible and leave residual cash explicitly stated.
The output should be phrased as an approval-ready execution plan: what the agent will submit to Interactive Brokers after human approval, not merely a list of ideas to consider.
# Secrets
Required v1 secrets:
FISCAL_AI_API_KEYANTHROPIC_API_KEYor provider-specific key required by the selected
pydantic-ai Claude model configuration.
- Interactive Brokers API credentials/configuration required by the selected
IBKR integration mode.
TIGRIS_ACCESS_KEY_IDTIGRIS_SECRET_ACCESS_KEY
Secrets must be read through centralized environment access. They must never be printed, logged, written to generated reports, included in exceptions, or stored in local runtime artifacts.
The existing src/investobot/env.py pattern should be extended rather than adding ad hoc os.getenv reads across the codebase.
Non-secret artifact storage configuration, such as TIGRIS_BUCKET_NAME, TIGRIS_ENDPOINT_URL, and TIGRIS_REGION, should also be centralized in src/investobot/env.py.
# External Data
# Fiscal.ai
Use Fiscal.ai as the primary financial data provider.
Fiscal.ai confirmed that ETF securities such as XLE are not currently included in the API. For v1, ETF securities are therefore unsupported by the investment analyst agent. Fiscal-backed analysis must operate on individual companies only. McIntosh-mentioned ETFs may still be used as thematic context or benchmark references, but they must not be selected for the final portfolio until a separate ETF-capable data source is approved and implemented.
Relevant documented endpoints include:
GET /v2/companies-listfor available securities and metadata.GET /v2/company/profilefor company profile, sector, industry, and dataset
availability.
GET /v1/company/financials/*/standardizedfor comparable income statement,
balance sheet, and cash flow statement data.
GET /v1/company/financials/*/as-reportedfor filing-traceable disclosed
line items.
GET /v1/company/ratiosandGET /v1/company/ratios/daily/{ratioId}for
point-in-time valuation and profitability ratios.
GET /v1/company/shares-outstandingfor share count data.GET /v1/company/adjusted-metricsfor adjusted operating metrics.GET /v2/company/stock-pricesfor daily prices.GET /v2/company/filingsplus filing source PDF/image endpoints for raw
filing review.
API keys should be sent through the X-Api-Key header when supported, not as a query parameter, to reduce accidental leakage through URLs.
# Interactive Brokers Purchaseability and Account Eligibility
Candidate securities must pass an Interactive Brokers suitability gate before they can appear in the final portfolio.
For v1, define this as a conservative two-step gate:
- Purchaseability check:
- Security must be listed on a major exchange accessible through Interactive
Brokers in the agent's account.
- Security must have reliable ticker, exchange, currency, and contract
identifiers suitable for deterministic IBKR order submission.
- Security must have sufficient market data coverage to size a position.
- OTC-only, pink-sheet, halted, delisted, warrant, private placement, and
manually settled securities are excluded unless explicitly enabled later.
- Account eligibility check:
- Security must be valid for the agent's actual Interactive Brokers account
type, permissions, country, currency, and regulatory constraints.
- If the account is a Canadian registered account, the security must be
traded on a designated stock exchange or otherwise meet qualified investment rules for that account type.
- The designated-exchange basis must be checked against current Government
of Canada / Department of Finance guidance, not inferred only from ticker format.
- Primary authoritative source suggestion: use the Department of Finance
designated stock exchanges list to determine whether the listing venue is designated, and cross-check CRA qualified-investment guidance for the account-level rule when relevant.
- Broker-level purchaseability should be checked separately through IBKR
contract lookup and a non-transmitting order preview/what-if path where available.
- If eligibility cannot be determined with high confidence, exclude it from
the final portfolio and place it in a watchlist/rejected section.
Important v1 implication: several McIntosh-mentioned securities may be useful for thematic context but should not automatically enter the portfolio because foreign exchange access, IBKR account permissions, liquidity, or account eligibility may be uncertain.
Examples likely to need rejection or manual confirmation:
- Singapore, Oslo, London AIM, Malaysia, Hong Kong, and small foreign listings.
- Illiquid juniors.
- Options spreads and leveraged inverse products.
- Any security requiring uncertain settlement or special broker handling.
The final report must state which McIntosh ideas were excluded and why.
# Broker Execution via Interactive Brokers
Interactive Brokers is the v1 execution broker for the agent's own trading account. The broker integration must be treated as an execution subsystem, not as an analysis shortcut.
Required execution flow:
- The analyst run produces a proposed portfolio and exact proposed order set.
- The web app displays the proposed orders, sizing, prices used, risks, source
references, and any assumptions that affect execution.
- The human explicitly approves or rejects the exact order set.
- After approval, the agent may submit only the approved orders through
Interactive Brokers.
- The system records submitted order ids, broker status, fills, partial fills,
rejects, cancellation results, and final reconciled positions.
- The strategy becomes
implementedonly after broker/account state is
reconciled against the approved decision record.
Execution guardrails:
- Never transmit an IBKR order before the strategy reaches
approved_for_broker_execution.
- Use IBKR paper trading, sandbox, or non-transmitting what-if/order-preview
paths for development and tests where available.
- Default to limit orders or another explicitly documented conservative order
type; do not use market orders unless the user explicitly approves that policy.
- Do not change margin, options, shorting, trading permissions, account settings,
or account funding through the agent.
- Store only broker metadata needed for auditability and reconciliation. Never
store raw credentials, session cookies, complete account identifiers, or raw broker response bodies in reports or durable artifacts.
- If any submitted order is rejected, partially filled, cancelled, or stale,
surface a visible reconciliation state instead of silently treating the strategy as implemented.
# Candidate Universe
Candidate generation should merge:
- Direct McIntosh tickers from recent issues.
- Liquid, Interactive-Brokers-accessible proxies for sectors McIntosh favors.
- Large-cap anchors that express the same thesis with lower operational and
liquidity risk.
Initial v1 universe should prioritize:
- Integrated oil majors.
- Liquid North American energy infrastructure and services names.
- Canadian-listed equivalents when they exist, are liquid, Interactive Brokers
purchaseable, account eligible, and express the same thesis. If no suitable Canadian-listed equivalent exists, use the US-listed entity.
Candidate examples for investigation, not automatic inclusion:
XOM,CVX,RIG,NE,NOV,FTI,OXY.XES,XOP,GUNR, andXLEare context/benchmark references only for
v1 because Fiscal.ai does not currently support ETF securities.
The agent may include a McIntosh-mentioned foreign listing only if the Interactive Brokers purchaseability and account-eligibility gates pass with high confidence.
# Investment Policy
The analyst should behave like a long-horizon capital allocator, not a trader.
Required policy:
- Minimum intended holding period for new purchases: 6 months.
- Default target holding period: 6-36 months, unless a thesis invalidation
trigger fires.
- Portfolio turnover should be low and explicitly justified.
- Cash is an allowed portfolio position when the agent believes risk/reward is
poor or when McIntosh's thesis implies waiting for a dislocation.
- Technical analysis may affect entry timing, position size, and cash reserve,
but should not produce standalone short-term trades.
- The agent should prefer waiting in cash over forcing marginal purchases that
do not clear the fundamental, eligibility, liquidity, and thesis-fit gates.
- The agent should define thesis invalidation triggers at purchase time so that
future runs can distinguish genuine long-term thesis breaks from ordinary volatility.
- Minimal hedging is allowed in v1, but should be conservative, explicit, and
secondary to the core investment thesis. Preferred hedges are cash reserves, reduced gross exposure, defensive eligible holdings, or a small allocation to a liquid non-leveraged hedge instrument that is suitable for a 6-month minimum holding period. The hedge rationale, sizing, expected behavior, and risk must be documented.
Disallowed in v1:
- Day trades and swing trades.
- Options strategies.
- Short sales.
- Leveraged ETFs.
- Margin.
- Trades whose only basis is chart momentum.
# Fundamental Methodology
Fundamental analysis has priority over technical analysis.
Each final portfolio holding should receive a score from 0-100 composed of:
- Business quality: 20%
- Balance sheet strength: 15%
- Cash generation and capital discipline: 20%
- Valuation: 20%
- Thesis fit: 15%
- Governance, jurisdiction, and security-specific risk: 10%
Required checks:
- Revenue, EBITDA/operating income, net income, free cash flow, and margin
trends across annual and recent quarterly periods.
- Net debt, interest coverage, maturity risk, and liquidity.
- Capital expenditure trends and whether they support or weaken the energy
capex super-cycle thesis.
- Return on invested capital or a defensible sector-specific proxy.
- Valuation versus the company's own history and relevant peers.
- Dividend/buyback sustainability where relevant.
- Commodity price sensitivity and downside case.
- Currency, jurisdiction, tax, and political risks.
- Data freshness and confidence level.
ETF analysis is out of scope for v1 because Fiscal.ai does not currently support ETF securities such as XLE. If ETF support is added in a later version through an approved ETF-capable data provider, ETF analysis should focus on:
- Holdings concentration.
- Sector/subsector exposure.
- Geographic exposure.
- Expense ratio.
- Liquidity and spread risk.
- Overlap with other proposed holdings.
- Whether the ETF actually expresses the McIntosh thesis or is only a loose
proxy.
# Filing Analysis Methodology
Use pydantic-ai with Claude Opus 4.7 to analyze raw company filings. The initial model id should be claude-opus-4-7, verified against Anthropic's current model documentation during implementation.
The filing-analysis step should:
- Retrieve recent annual/interim filings through Fiscal.ai where available.
- Provide the model only the relevant filing text or extracted sections needed
for the analysis.
- Ask for structured output matching a local Pydantic model, such as:
- key business drivers,
- segment exposure,
- debt and liquidity concerns,
- capex plans,
- management risk disclosures,
- commodity and geopolitical sensitivity,
- direct evidence supporting or contradicting the thesis,
- confidence and missing-data flags.
- Store only summarized, non-secret structured outputs.
Raw filing text may be cached only if explicitly approved later. The v1 default should avoid storing full filing bodies.
Dependency note: adding pydantic-ai and Anthropic provider dependencies will require explicit dependency approval before implementation, exact pins in pyproject.toml, uv lock, and the full check suite.
Cost-control note: the implementation should record token usage and estimated cost per run when provider metadata is available. A hard per-run budget cap is not specified in this draft.
Dependency note: Polars is part of the intended v1 data-science stack. It still must be added with an exact pin, lockfile update, and the full check suite.
# Technical Methodology
Technical analysis is a secondary timing and risk-control input.
Each candidate should receive a 0-100 technical score composed of:
- Trend: 35%
- Relative strength: 25%
- Momentum: 20%
- Volatility/drawdown risk: 10%
- Liquidity/tradability: 10%
Minimum indicators:
- 50-day and 200-day simple moving averages.
- 20-day and 60-day price momentum.
- 52-week high/low position.
- Relative strength versus
SPYwhere data is available and against a
comparable company peer basket. ETF-relative strength, including against XLE, is deferred until an ETF-capable data source is implemented.
- 20-day average dollar volume.
- Recent maximum drawdown over 6 and 12 months.
Technical analysis should not override a failing fundamental or eligibility screen. It can affect sizing, entry timing, watchlist placement, and the amount held in cash. A poor technical setup may defer deployment, but it should not turn the system into a short-term trading strategy.
# Portfolio Construction
The portfolio should be long-only in v1.
Default constraints:
- Starting capital: USD 10,000.
- Maximum single holding: 25%.
- Minimum final holding size: 5%, unless used as a small speculative sleeve.
- Maximum speculative or high-volatility single-name sleeve: 10% total.
- Minimum residual cash target: 2%.
- Maximum residual cash target: 15% during normal deployment.
- Cash may exceed 15% only when the agent explicitly decides that capital should
wait for a major expected market move or when too few eligible candidates pass the investment gates.
- Maximum explicit hedge allocation: 5%, excluding ordinary cash reserves.
- No options in v1.
- No margin.
- No short positions.
- No leveraged ETFs unless explicitly enabled in a future version.
- No ETF holdings in v1 because Fiscal.ai does not currently support ETF
securities.
- Minimum intended holding period: 6 months.
The portfolio should generally express the latest McIntosh thesis using liquid, eligible instruments:
- Core thesis exposure through eligible individual companies.
- Lower-risk large-cap anchors.
- Small satellite allocations only where security-specific risk is acceptable.
- Explicit cash reserve for volatility, major market dislocations, or future
thesis-aligned opportunities.
For each proposed holding, output:
- Ticker and exchange.
- Company or fund name.
- Thesis role.
- Allocation percentage.
- Approximate USD allocation.
- Latest price used.
- Whole-share quantity.
- Residual cash impact.
- Fundamental score.
- Technical score.
- Combined score.
- Minimum intended holding period.
- Target review date.
- Main risks.
- Invalidation triggers.
Rejected candidates should include:
- Candidate ticker.
- Source issue.
- Reason for rejection.
- Whether it should remain on a watchlist.
# Output Report
The primary report output is a marimo WebAssembly HTML document:
analyst-runs/<run-id>/report.html
The generated marimo notebook source should also be retained:
analyst-runs/<run-id>/report.py
Required sections:
- Executive summary.
- Upstream McIntosh thesis summary.
- Candidate universe and exclusions.
- Fundamental methodology.
- Technical methodology.
- Security-level analysis.
- Final USD 10,000 portfolio decision.
- Residual cash.
- Risks and invalidation triggers.
- Human approval instructions and proposed broker order set.
- Source reports, data sources, and timestamps.
- Execution-boundary notes.
The report should avoid raw API payloads and model transcripts. It should be safe to serve from the Flask app to the authenticated user, but it should still be treated as sensitive because it contains financial strategy and execution instructions.
Machine-readable outputs:
portfolio.json: final holdings and sizing.candidates.json: analyzed candidates, scores, and rejection reasons.methodology.json: scoring weights, thresholds, and data timestamps.sources.json: newsletter source files and external data references used.decision.json: latest final decision summary for the Flask app.
Database persistence is authoritative for web-app status display. JSON/report artifacts in Tigris support auditability and richer rendering, but the web app should not need to parse the marimo report to know whether a strategy is in progress, awaiting human approval, approved for broker execution, in broker execution, or implemented.
# Security and Privacy Requirements
- Read all secrets through centralized environment configuration.
- Never include API keys, OAuth tokens, cookies, authorization headers, full
callback URLs, or raw upstream response bodies in logs or reports.
- Sanitize external API errors before display.
- Server logs may include method, URL without query string, status code, reason
phrase, content type, and response length.
- Generated reports must be treated as sensitive because they may reveal
financial intent and portfolio recommendations.
- Do not add telemetry, analytics, external scripts, or remote logging.
- Do not make Interactive Brokers, Questrade, Fiscal.ai, or AI-provider API
calls from browser code.
- Never submit broker orders without a persisted human approval event tied to
the exact approved order set.
# Error Handling
The agent should fail closed when eligibility, pricing, or data freshness is uncertain.
Examples:
- Missing Fiscal.ai key: exit with a generic configuration error.
- Fiscal.ai 401/403: report authentication/configuration failure without echoing
the key or request URL query.
- Missing company data: mark candidate as incomplete and exclude from final
portfolio unless there is another reliable data source approved later.
- Eligibility uncertain: reject from portfolio and include in watchlist.
- Stale price data: reject or reduce to watchlist depending on severity.
- Model filing analysis failure: continue with numeric analysis, mark filing
confidence low, and do not overweight the candidate.
- Tigris upload failure: keep the strategy in a non-submitted failure state,
store only sanitized error metadata, and do not expose local temporary paths.
- Analyst-service interruption: keep the associated
PortfolioStrategyvisible
as analysis_in_progress with updated timestamp and sanitized error metadata if failure can be detected.
- Attempted new run while a strategy is awaiting human approval or approved for
broker execution: reject by default unless the request explicitly cancels/supersedes the awaiting strategy first. Persist cancelled_at and make the cancelled strategy visible in the strategy history.
- Attempted supersession after live broker orders have been submitted: require
explicit reconciliation of submitted orders, fills, partial fills, and account state before a replacement strategy can become executable.
# Testing Strategy
Unit tests:
- McIntosh issue discovery and descending issue-number sort.
- Ticker and sector extraction from Markdown fixtures.
- Environment loading without leaking secret values.
- Fiscal.ai client request construction uses headers for API keys.
- Fiscal.ai error sanitization.
- Tigris artifact client stores objects under the expected run prefix and
redacts storage credentials from errors.
- Eligibility gate behavior.
- Fundamental score calculation.
- Technical indicator calculation.
- Portfolio sizing with whole shares and residual cash.
- Report serialization excludes raw payloads and secrets.
- Marimo report generation consumes structured artifacts without live external
API calls.
- Artifact upload persists object keys to the strategy row and does not depend
on local durable storage.
- Source-report tracking includes every newsletter issue/report used in the run.
- Decision serialization includes human approval instructions, proposed broker
order set, and holding horizon.
PortfolioStrategystatus transition validation.PortfolioStrategyHoldingpersistence from a final portfolio decision.PortfolioStrategySourcepersistence from parsed McIntosh issues.- Web status query returns the active strategy without parsing report files.
- Strategy supersession marks the prior awaiting strategy as
cancelledbefore
creating a replacement in-progress strategy.
Integration tests:
- Run against recorded sanitized Fiscal.ai-like fixtures.
- Build a complete report from fixture McIntosh issues and fixture market data.
- Confirm foreign/uncertain listings are rejected.
- Confirm the latest completed
decision.jsoncan be read by the web app
without starting an analyst run in the Flask process.
- Confirm an analysis run creates
analysis_in_progress, then moves to
strategy_submitted_awaiting_human_approval only after a complete decision is persisted.
- Confirm human approval moves the strategy from
strategy_submitted_awaiting_human_approval to approved_for_broker_execution.
- Confirm broker execution moves approved strategies through
broker_execution_in_progress to implemented using fixture-backed broker responses.
- Confirm a superseding run marks the prior awaiting-approval or
approved-but-unsubmitted strategy as cancelled before creating the new analysis_in_progress strategy.
- Confirm the generated marimo report exports to WebAssembly HTML and can be
uploaded to Tigris with its required assets.
- Confirm the Flask app can serve or link authenticated Tigris-backed artifacts
without relying on local files.
No live Fiscal.ai, Anthropic, Questrade, or Interactive Brokers calls should run in the default test suite.
# Acceptance Criteria
v1 is complete when:
- A CLI command runs outside the Flask server process.
- An authenticated analyst-service endpoint can trigger a bounded analysis run
and return a run identifier.
- Triggering a run creates a durable
PortfolioStrategyrow with
analysis_in_progress.
- Successful analysis persists the overarching strategy recommendation,
holdings, source references, artifact object keys, and status strategy_submitted_awaiting_human_approval.
- The web app exposes the active strategy status visually.
- The human can explicitly approve an awaiting strategy for broker execution.
- The agent can submit only the approved order set through Interactive Brokers
and reconcile fixture-backed order/fill state before marking the strategy implemented.
- A submitted strategy can be superseded only by first marking the prior
strategy as cancelled.
- The Flask app can display the latest completed analyst decision without
running analysis inside the web process.
- The agent reads
analyses/mcintosh/, selects the largest issue number as the
highest-priority source, and incorporates recent context.
- The agent records every newsletter issue/report used as a source reference in
the final report and sources.json.
- The McIntosh run can accept an explicit list of issue numbers for targeted
strategy updates.
- The agent requires Fiscal.ai, Claude/pydantic-ai, and Interactive Brokers
credentials/configuration through secure environment configuration.
- The candidate universe includes newsletter-sourced names plus liquid eligible
proxies.
- Each final holding passes Interactive Brokers purchaseability and account
eligibility gates or is excluded.
- Fundamental and technical scoring are documented and implemented.
- Fundamental analysis has higher weight than technical analysis.
- The final decision includes a minimum intended holding period of at least 6
months for every purchase.
- Cash can be selected as an intentional allocation when the agent is positioning
for a major market move.
- Minimal hedging is supported within the v1 constraints and explicitly capped.
- Raw filings are summarized through structured pydantic-ai outputs.
- A final USD 10,000 whole-share portfolio decision and residual cash estimate
are produced.
- A marimo report notebook and exported WebAssembly HTML report are generated
with charts, tables, and source references.
- The generated
report.html, requiredassets/, notebook source, and JSON
artifacts are uploaded to Tigris under the run prefix.
- The Flask app serves or links the Tigris-backed
report.htmland required
assets/ over HTTP for the authenticated user.
- Human approval instructions and an exact proposed Interactive Brokers order
set are produced before any broker submission is possible.
- Rejected McIntosh ideas are listed with reasons.
- Database rows are authoritative for status and latest-strategy display.
- Marimo report, exported assets, and JSON artifacts are stored in Tigris under
analyst-runs/<run-id>/.
- The standard checks pass:
uv run ruff format --check .
uv run ruff check .
uv run pyright
uv run pytest -q
# Resolved Decisions
- Filing analysis model: Claude Opus 4.7, using model id
claude-opus-4-7 after verifying current Anthropic docs during implementation.
- Account eligibility source: use the Department of Finance designated stock
exchanges list and CRA qualified-investment guidance as primary rule sources when the agent account type requires it. Use Interactive Brokers contract lookup and non-transmitting order preview/what-if capability for broker purchaseability where available.
- Listing preference: bias to Canadian-listed equivalents when they exist and
are liquid, eligible, and thesis-equivalent. Otherwise use the US-listed security.
- Hedging: allow minimal hedging in v1 under strict sizing and eligibility
constraints.
- Source selection: implement McIntosh first, support explicit issue-number
lists for targeted strategy updates, and default to the latest 3 McIntosh issues when no list is provided. Future newsletters should be added as explicit source adapters reading from source-specific analyses/<source>/ folders.
- Compliance/account-owner boundary: no extra compliance workflow in v1.
- Stable execution-boundary template: descoped for v1.
- Supersession: a submitted strategy may be superseded only after the prior
strategy is marked cancelled, unless live broker orders have already been submitted; then explicit order/fill reconciliation is required.
- Artifact persistence: use Tigris object storage on Fly.io. Do not rely on
persistent volumes or local durable artifact paths.
# Phased Implementation Plan
# Phase 1: Data Model and Status UI Foundation
Implement Piccolo tables and migrations for PortfolioStrategy, PortfolioStrategyHolding, and PortfolioStrategySource. Add status transition helpers for analysis_in_progress, strategy_submitted_awaiting_human_approval, approved_for_broker_execution, broker_execution_in_progress, implemented, and cancelled. Add a minimal Flask view that shows the active strategy status and latest persisted metadata.
Next phase gate:
- All standard checks pass.
- Database migration behavior is covered by tests.
- Human approval is received before starting Phase 2.
# Phase 2: Analyst Service Shell
Add the analyst CLI and separate service endpoint skeleton. The endpoint should create an analysis_in_progress strategy row, return a run id, reject ambiguous concurrent runs, and support explicit cancellation/supersession before a new run. Add centralized Tigris configuration fields to environment loading, but do not perform live object-storage writes yet.
Next phase gate:
- All standard checks pass.
- Endpoint and CLI behavior are covered by tests without external API calls.
- Environment loading covers Tigris bucket and S3-compatible settings without
leaking credentials.
- Human approval is received before starting Phase 3.
# Phase 3: McIntosh Ingestion and Source Tracking
Implement issue discovery, strict issue-number parsing, default latest-3 selection, explicit issue-number selection, source summarization, and PortfolioStrategySource persistence.
Next phase gate:
- All standard checks pass.
- Fixture tests prove newest-issue bias and explicit issue selection.
- Human approval is received before starting Phase 4.
# Phase 4: Fiscal.ai Client, Eligibility Gates, and Data Fixtures
Implement the Fiscal.ai client with sanitized errors and secure API-key header handling. Add Interactive Brokers purchaseability and account-eligibility gates using IBKR contract metadata/order preview plus Government of Canada/CRA rule sources where account type requires them. Build sanitized fixtures so the default test suite never makes live Fiscal.ai, Anthropic, Questrade, or Interactive Brokers calls. Do not add v1 ETF support; Fiscal.ai does not currently include ETF securities such as XLE, so ETFs should be treated as unsupported candidates unless a separate approved ETF-capable provider is introduced in a future phase.
Next phase gate:
- All standard checks pass.
- Security tests confirm no secrets or raw upstream payloads are serialized.
- Human approval is received before starting Phase 5.
# Phase 5: Fundamental, Technical, and Portfolio Engine
Implement Polars-based analysis, fundamental scoring, technical scoring, Canadian-listed-equivalent preference, unsupported-ETF rejection, minimal hedging constraints, cash positioning, whole-share sizing, and final strategy persistence.
Next phase gate:
- All standard checks pass.
- Portfolio tests cover whole-share sizing, cash, minimum 6-month holding
periods, hedging cap, and rejected candidates.
- Human approval is received before starting Phase 6.
# Phase 6: Filing Analysis with pydantic-ai and Claude Opus 4.7
Add structured filing-analysis models and pydantic-ai integration with Claude Opus 4.7. Store only summarized structured outputs, token usage, estimated cost when available, confidence flags, and missing-data notes.
Next phase gate:
- Explicit dependency approval is received before adding pydantic-ai and any
Anthropic provider package.
- All standard checks pass after dependency lockfile updates.
- Human approval is received before starting Phase 7.
# Phase 7: Local Marimo Analysis Notebook
Introduce marimo as a local analyst-review surface before adding durable artifact storage or browser-served reports. Generate a local marimo notebook from the completed strategy inputs and outputs so the agent can conduct an analysis and the human can run the notebook locally for assessment.
This phase is intentionally local-only:
- Do not export WebAssembly HTML.
- Do not upload notebook files, report assets, or derived artifacts to Tigris.
- Do not rely on Fly.io persistent volumes for durable storage.
- Do not serve the notebook through the Flask app.
- Do not make the notebook the final user-facing report format yet.
The notebook should be generated into a local ignored working area, such as data/analyst-runs/<run-id>/analysis.py, and should be reproducible from persisted database state plus locally available source files. It should include the McIntosh thesis context, candidate table, Fiscal-backed fundamental and technical score outputs, rejected candidate notes, proposed holdings, residual cash, and proposed Interactive Brokers order instructions for human approval.
Next phase gate:
- Explicit dependency approval is received before adding marimo or any charting
package.
- All standard checks pass after dependency lockfile updates.
- A fixture-backed notebook generation test proves no network calls are made
during notebook rendering/generation.
- Human runs the generated marimo notebook locally and approves the analysis
review workflow before starting Phase 8.
# Phase 8: Tigris Artifact Storage
Implement an artifact storage layer for Tigris using S3-compatible APIs. Upload JSON artifacts, generated notebook source, exported report HTML, and report assets under analyst-runs/<run-id>/. Persist object keys on PortfolioStrategy. Add authenticated Flask routes or signed-URL support for retrieving report artifacts without relying on local files.
Next phase gate:
- Explicit dependency approval is received before adding a Python S3-compatible
storage client.
- All standard checks pass after dependency lockfile updates.
- Fixture-backed storage tests prove object keys are written and credentials are
redacted from errors.
- Human approval is received before starting Phase 9.
# Phase 9: Marimo WebAssembly Report
Generate a marimo report.py from persisted structured artifacts, export it to report.html with marimo export html-wasm, include required assets/, upload the report artifacts to Tigris, and serve the report through the Flask app for the authenticated user.
Next phase gate:
- Explicit dependency approval is received before adding marimo or charting
packages.
- All standard checks pass after dependency lockfile updates.
- Browser verification confirms the Tigris-backed exported report renders charts
and tables.
- Human approval is received before starting Phase 10.
# Phase 10: Interactive Brokers Execution with Human Approval
Implement the broker execution subsystem for Interactive Brokers. The first implementation should support fixture-backed contract lookup, order preview or what-if validation where available, persisted human approval events, exact order set hashing/comparison, order submission after approval, submitted-order tracking, fill/reject/partial-fill reconciliation, and visible execution status in the Flask app.
This phase must not broaden the trading policy. It should preserve the v1 long-only, no-margin, no-options, no-shorting constraints unless the spec is explicitly changed later.
Next phase gate:
- Explicit approval is received before adding any Interactive Brokers client
dependency or integration mode.
- All standard checks pass after dependency/configuration changes.
- Fixture-backed tests prove no live IBKR calls run in the default suite.
- Browser verification confirms the approval UI cannot submit unapproved or
changed order sets.
- Human approval is received before starting Phase 11.
# Phase 11: Fly.io Deployment Shape and Hardening
Wire the analyst service as a separate Fly process/machine from the Flask web server. Add operational safeguards for bounded runs, sanitized errors, visible failure state, cancellation/supersession, Tigris artifact object keys, active strategy display, human approval, and broker execution reconciliation.
Next phase gate:
- All standard checks pass.
- Fly deployment config, Tigris bucket/secrets setup, and Interactive Brokers
secret handling are reviewed.
- A full fixture-backed end-to-end run creates a submitted strategy, holdings,
source references, Tigris-backed JSON artifacts, marimo report, proposed broker order set, human approval record, and reconciled broker-execution result.
- Human approval is received before considering v1 complete.
# Standard Checks
Every phase gate requires:
uv run ruff format --check .
uv run ruff check .
uv run pyright
uv run pytest -q
# References
- Fly.io Tigris docs: https://fly.io/docs/tigris/
- Fly.io
fly storagedocs: https://fly.io/docs/flyctl/storage/ - Fiscal.ai API documentation: https://docs.fiscal.ai/docs/introduction
- Fiscal.ai API reference: https://docs.fiscal.ai/docs/api-reference
- Anthropic Claude Opus 4.7: https://www.anthropic.com/claude/opus
- Anthropic Claude model docs:
https://platform.claude.com/docs/en/about-claude/models/whats-new-claude-4-6
- Polars user guide: https://docs.pola.rs/
- marimo documentation: https://docs.marimo.io/
- marimo WebAssembly HTML export:
https://docs.marimo.io/guides/exporting/webassembly_html/
- Streamlit embedding documentation:
https://docs.streamlit.io/deploy/streamlit-community-cloud/share-your-app/embed-your-app
- Canada Revenue Agency TFSA permitted investment guidance:
https://www.canada.ca/en/revenue-agency/services/tax/individuals/topics/tax-free-savings-account/types-investments.html
- Department of Finance designated stock exchanges:
https://www.canada.ca/en/department-finance/services/designated-stock-exchanges.html
- Local McIntosh analyses:
analyses/mcintosh/