Skip to main content
Glama

Trading Desk

Multi-agent equity research for US markets, running on Claude Code subagents over MCP. Analysis-first: real money is never touched, and order flow goes to an Alpaca paper account.

Why this exists

Financial data is easy to fetch and easy to get wrong. US-GAAP XBRL concepts drift per company and over time, so reading a single hardcoded concept returns figures that are years stale with no error raised:

Ticker

Live concept

Correct FY revenue

Naive Revenues lookup

AAPL

RevenueFromContractWithCustomerExcludingAssessedTax

$416.2B (FY25)

$62.9B — a 2018 figure

NVDA

Revenues

$215.9B (FY26)

correct, coincidentally

MSFT

RevenueFromContractWithCustomerExcludingAssessedTax

$331.8B (FY26)

$16.0B — a 2010 figure

An agent given $62.9B for Apple writes a confident, completely wrong thesis. The whole design follows from preventing that.

Operating rules

  1. Agents never compute and never recall. Every number comes from a tool call; ratios and indicators are calculated in Python.

  2. Every value carries provenance — XBRL concept, period, form, accession, filing date — so any figure can be traced back to the filing.

  3. Stale data raises. A number too old to answer the question is an error, not a footnote.

  4. Concept resolution is code with tests, not a prompt instruction.

Related MCP server: ReadyTrader-Stocks

Status

Complete. Research, sizing, a scoreable record, the context around a name, paper execution that cannot be reached except through an approved thesis, and a loop that grades the calls afterwards.

  • EDGAR client — rate limited, disk cached, no API key required

  • Concept resolver with provenance and staleness enforcement

  • Golden tests (23) pinned to filed 10-Ks

  • desk MCP server, verified over stdio

  • Phase 1 — fundamentals + chartist agents, /analyze

  • Phase 2 — risk (veto) + journal, /journal

  • Phase 3 — filings, macro, screener

  • Phase 4 — pilot, Alpaca paper execution behind risk approval

  • Phase 5 — /postmortem calibration loop

  • Phase 6 — capitol, oracle, ledger

  • Phase 7 — /nimbus orchestrator

The agents

Ten specialists, each with a narrow remit and a matching tool allowlist. None of them computes anything; every figure comes from a tool call.

Agent

Answers

Explicitly does not

fundamentals

Is this a business worth owning?

Price, timing

chartist

Is this a reasonable moment?

Business quality

filings

What did the company actually say?

Compute ratios

macro

Does the environment support this?

Individual companies

screener

Which names are worth researching?

Analyse them

capitol

What are better-informed holders doing?

Business, chart, price

oracle

How far, and how likely?

Which way

risk

How large, and may it be taken at all?

Judge the thesis

pilot

What did the broker actually do?

Hold any view

ledger

What does the book say, and is it intact?

Hold any view

Orchestration

/nimbus is the front door. It takes any request — a ticker, a question, a half-formed worry — routes it to the specialists that can answer it, and reconciles what comes back into one view.

It runs in the main thread rather than as a subagent, deliberately: an agent dispatching agents nests badly, and parallel dispatch is only available where the conversation is. Independent specialists go out in a single message and come back concurrently; their independence is what makes agreement between them worth anything.

Nimbus holds no view of its own. Every claim in its output came back from a specialist in that session, and a figure it cannot attribute to a tool call is one it must not state. It also defers rather than rebuilds — a full research pass on a ticker is handed to /analyze, not reimplemented.

Command

Does

/nimbus

Routes anything to the right specialists and reconciles the answers

/analyze

Full research pass on one ticker, sized and journalled

/journal

Reads, records and closes calls

/postmortem

Grades the closed book and reports what to change

Money moves through exactly one sequence, and the orchestrator never shortens it:

research → risk approves → thesis journalled → place_order(thesis_id)

A request to "just buy 100 shares" has no way to be expressed — place_order takes a thesis id and nothing else.

The dashboard

A local Streamlit app over the same desk_mcp modules the agents call:

uv run --extra ui streamlit run desk_ui/Home.py

No language model runs in it. Everything it shows — financials, metrics, technicals, sizing, forecasts, 13F and congressional disclosures, the journal, the scoreboard, desk health — is deterministic Python. Judgement stays in Claude Code, where the agents live; the dashboard is for the parts that were never judgement to begin with.

Page

Shows

Home

Open calls, capital at risk, book integrity, missing credentials

Research

Financials with their XBRL concepts, derived metrics, technicals, filings

Scoreboard

Expectancy, calibration, per-call review

Sizing & odds

Position sizing against the limits, and the probabilities the levels imply

Smart money

13F books with quarter-over-quarter changes, congressional trades, Form 4

Market

Macro snapshot, movers, relative strength, expected move

Journal

Record and close calls

The app inherits the desk's rules rather than restating them. Figures carry the period and the concept they came from. A failed call renders as a named gap, because an empty table reads as an answer. limitations are rendered inline and never behind a collapsed panel — a limitation nobody reads is one that does not exist.

Two journal rules are enforced in the form, since this is the one place a call can be written without an agent having read them first: a thesis will not record without a falsifier, and a position will not close without an exit price. Both are tested.

Exposing it

It binds to 127.0.0.1 via .streamlit/config.toml. Streamlit's own default is 0.0.0.0, which publishes an external URL — unsuitable here, because the app writes to the journal and Streamlit ships no authentication. Anyone reaching the URL would be the operator.

Hosting it later means a reverse proxy with real authentication in front, and DESK_UI_READONLY=1 unless writes are genuinely needed:

DESK_UI_READONLY=1 uv run --extra ui streamlit run desk_ui/Home.py

Changing the bind address alone is not enough.

Setup

uv sync
uv run pytest tests/golden -q     # must be green before any agent work

Credentials

Keys live in the environment, never in the repo — .mcp.json reads them via ${VAR} expansion. Put them in ~/.zshrc so they cannot be committed by accident.

Variable

Where to get it

Needed by

ALPACA_API_KEY / ALPACA_SECRET_KEY

app.alpaca.markets — switch the dashboard to Paper, then API Keys → Generate. Secret shows once.

Phase 1 (chartist)

FRED_API_KEY

fredaccount.stlouisfed.org/apikeys — instant, free

macro agent. Everything else runs without it

SEC_USER_AGENT

Your own name email

optional; defaults in .mcp.json

Paper trading needs no funding, identity check or approval — that applies only to live accounts.

Verify without exposing anything (the script prints presence and a masked prefix only, never a secret):

uv run python scripts/check_keys.py

Data sources

Source

Provides

Auth

SEC EDGAR

XBRL financials, filings, Form 4 insider, 13F institutional

none (User-Agent only)

Alpaca

Bars, quotes, news, paper orders/positions

free API key

FRED

Rates, curve, inflation

free API key

House Clerk

Congressional STOCK Act disclosures

none

Deliberately not used: yfinance (unofficial endpoints, silent empty responses) and Stooq (now behind a JS proof-of-work wall). EDGAR is preferred for fundamentals because it is the source of record rather than a scrape of it.

MCP tools

Tool

Returns

get_financials

Full statement, 17 line items, each with provenance

get_financial_history

Historical series for one line item

get_metrics

Margins, returns, growth, leverage, cash conversion

get_technicals

Trend, momentum, volatility, support and resistance

list_line_items

Supported line item keys

get_filings

Recent filings, optionally filtered by form

get_filing_text

A filing's text as filed, markup stripped, in windows

search_filing_text

Verbatim passages around a term in a filing

get_insider_activity

Form 4 insider transactions

get_macro_snapshot

Rates, curve, inflation, jobs, vol, dollar — with changes

get_macro_series

One macro series in detail

get_market_movers / get_most_active

Discovery lists from the tape

rank_candidates

Orders a symbol list by relative strength

size_position

Share count, capital at risk, every limit checked, verdict

get_risk_policy

The standing limits, each with its rationale

journal_thesis

Records a call with its evidence and falsifiers

list_theses / get_thesis

The book, open or closed

close_thesis

Resolves a call and computes realised R

get_account

Paper account equity, cash, buying power

place_order

Sends an approved thesis to the paper broker

get_broker_positions / get_broker_orders

Broker state

cancel_order / close_broker_position

Unwind

reconcile_positions

Broker positions against journalled theses

score_book

Expectancy, win rate, payoff and calibration across closed calls

review_thesis

One call with its plan, its result and the gap between them

get_institutional_holdings

A manager's 13F book and its quarter-over-quarter changes

get_congress_trades

STOCK Act disclosures, filtered by ticker or member

get_congress_activity

Names appearing most across recent congressional filings

get_price_distribution

Expected move and terminal distribution over a horizon

get_path_probabilities

Odds of target before stop, and the breakeven win rate

get_desk_health

The desk's own record-keeping, audited for silent failures

Risk limits

The risk agent holds a veto and does not exercise judgement about it — the limits are policy, checked in Python, and quoted back with the reason they exist. get_risk_policy returns them at runtime.

Limit

Value

Why

Risk per trade

1% of equity

Ten consecutive losses cost a tenth of the account, which is survivable

Position size

20% of equity

A tight stop makes a huge position look cheap; gaps do not respect stops

Portfolio heat

6% of equity

The total loss accepted if every open thesis fails at once

Reward:risk

2:1 minimum

Below it the strategy needs a win rate nobody here has demonstrated

Stop distance

1.5x ATR(14)

A stop inside daily noise is an exit schedule, not protection

Correlation is the one call the checks cannot make: three 1% positions on the same driver are one 3% position in disguise, and the agent is instructed to veto that even when every numeric limit passes.

The journal

Theses are JSON files under theses/, one per call, written at the moment the call is made — greppable, diffable, and reviewable alongside the code that produced them. A thesis will not record without at least one falsifier, and direction="watch" calls are recorded too: a watchlist name that ran away without you is as informative as a trade that failed, and only one of the two tends to get remembered. Realised R is computed on close from the recorded entry and stop, so outcomes compare across positions of different sizes.

Set DESK_THESES_DIR to keep the book somewhere other than the repository.

Smart money

Three populations, three different signals, and conflating them is how this data gets misread. All of it is disclosed late; the discipline is refusing to describe stale filings in the present tense.

Source

Lag

What the filer knows

Form 4 insider

2 business days

Legally presumed to know something

13F institutional

Quarter end, filed up to 45 days later

Managed the position six weeks ago

Congress PTR

Up to 45 days, often late

Frequently an outside manager, not the member

13Fs are aggregated per security. A manager with sub-advisers files the same issuer on many lines — Berkshire reports Apple twelve times — so reading the table row-wise reports a twelfth of the real position. Changes are computed from share counts, never values: a holding marked down by the market is not a sale, and treating it as one is simply wrong. Each position carries implied_price_per_share as a self-check, because filings before 2023 reported value in thousands rather than dollars.

Congressional amounts are statutory bands. $1,001 - $15,000 is what was filed; a midpoint is a number nobody reported. Transactions live inside filing PDFs, so they are parsed from extracted text and every row carries the document link. Scanned filings that extract to nothing are reported under unreadable_filings rather than dropped — a member whose filing cannot be read is not a member who did not trade.

Forecasting

oracle produces distributions, never predictions, and the reason is worth stating plainly.

Drift is assumed to be zero. Not because stocks do not rise, but because drift cannot be estimated from the data available: the standard error of a mean return measured from a year of daily data is roughly the annual volatility itself, so a measured 12% drift on a 30%-vol name carries an error bar of about ±30%. Volatility is estimable from the same sample to within a few percent. So the model forecasts the spread and refuses to forecast direction — which makes it useless for picking sides and useful for the questions intuition gets wrong.

Two models run on every question. Gaussian is analytically clean and wrong in the tails; bootstrap resamples the name's own history and carries its real skew. Where they disagree, the normal assumption is doing damage, and the gap is reported rather than averaged away.

The most decision-useful output is edge_vs_breakeven. A 2:1 setup must reach target a third of the time simply to break even. When the modelled probability sits below that, the geometry does not pay under zero drift and the entire case has to come from the thesis — which the model cannot see and will not pretend to.

analytic_unlimited_time is the closed form for a driftless walk, where the odds depend only on the log distances to each barrier. Simulated figures sit below it because the horizon expires; a large gap means the trade is not wrong, it is being given too little time.

Barriers are checked at daily closes, so an intraday spike through the stop that closes back inside is not counted — real stop-outs are somewhat more likely than reported. Results are seeded, so the same question returns the same answer; a probability that moved on refresh could not be quoted in a thesis or checked afterwards.

Desk health

get_desk_health audits the desk rather than a company, because the failures that corrupt every other number here are silent ones:

Finding

What it corrupts

Unparseable thesis file

list_theses skips it silently — the book is quietly short and heat is understated

Closed without an exit price

No realised R, ever; permanently outside every score_book figure

Open position with no dollar risk

Real exposure contributing nothing to portfolio heat

Open thesis past its horizon

Still consuming heat while nobody watches it

Missing credential

Names which tools will fail, before an agent commits to reasoning that needs them

It runs offline and checks credentials for presence, not validity — a revoked key passes here and fails at the call site. Values are never read into a response.

The calibration loop

/postmortem grades what the journal recorded. It asks two questions that are routinely confused for one:

Was the call right? Expectancy in R, win rate, payoff ratio — all computed in Python, all quoted rather than derived by an agent.

Was it right for the reason given? Every thesis stores falsifiers, and review_thesis returns them unchecked. Answering them means dispatching the chartist at the price level, or the filings agent at the disclosure, and finding out what actually happened. A thesis that worked because of something nobody in the evidence predicted is a losing process with a winning outcome, and it is the result most likely to be repeated.

Three numbers do the calibration work:

Reading

What it means when it breaks

conviction_ordering_holds

false — expectancy did not rise with stated conviction, so sizing up on conviction was paying for a signal that is not there

avg_r_capture

Realised R over planned R. Well below 1 means targets sit beyond where positions really get exited, and every approved reward:risk was optimistic

avg_days_held vs horizon

A swing call held four months was re-labelled after the fact, usually by not selling

Nothing that produced no R is quietly counted as a scratch. Watch calls have no R by design; a position closed without an exit price is a record-keeping failure. Both appear under unscored with the reason, and both are excluded from every performance figure rather than dragging it toward zero.

minimum_meaningful_sample is 20. Below it the skill is instructed to report the numbers as descriptive and draw no inference — three losing trades is not evidence of a broken process, and changing the rules on that basis is worse than doing nothing.

Findings propose changes; they do not make them. A limit that should move is a change to desk_mcp/risk.py, argued for separately — a rule rewritten in the same pass that discovered it has never been argued with.

Execution

Paper only. The base URL is the paper endpoint, hardcoded with no environment variable that redirects it, and the account number is checked for Alpaca's PA prefix before any order is sent — live keys against the paper URL fail closed rather than trading.

The gate is the shape of the function rather than an instruction to an agent:

place_order(thesis_id)      # and nothing else

There is no symbol parameter, no quantity, no price. All of it is read back out of the journal entry, which exists only because the risk officer approved it and only carries a share count the risk officer computed. An agent asked to buy 100 shares of something has no way to express that.

An order is refused when the thesis is closed, is a watch call, carries a vetoed verdict, was sized at zero shares, lacks an entry or stop, or already has an order attached — the last of which is what stops a retried call from opening a second position in the same name.

Orders go out as brackets, so the stop the risk officer sized against is submitted with the entry rather than left to a later call that might never happen. reconcile_positions compares what the broker holds against what the journal knows about, which is the only way to see the untracked exposure that portfolio heat is blind to.

Known limitations

  • Q4 quarterly gaps. Q4 gets no standalone 10-Q; it must be derived as FY minus Q1–Q3. Not yet implemented — quarterly series skip Q4.

  • Alpaca free tier is IEX-only (~2% of consolidated volume), so daily-bar volume is unreliable for swing setups. The price layer sits behind an adapter so Tiingo or another EOD source can be swapped in without touching agent code. Same seam later carries a non-US broker.

  • Non-US-GAAP filers (foreign issuers on IFRS) are not covered by the current concept registry.

  • The screener has no universe. It ranks a list you give it and reads the venue's movers and most-active tables. It cannot screen "all US software above $2B" — there is no fundamental universe behind it, and a themed list assembled by an agent is a recollection, not a screen. The screener agent is instructed to say which it is.

  • Filing text is text. get_filing_text strips markup and returns what was filed. Tables survive as readable rows, but nothing is parsed into figures — a number read out of filing prose has no XBRL concept behind it, so prefer get_financials whenever the figure exists there.

  • Macro needs its own key. Without FRED_API_KEY the macro tools fail with a message saying where to get one. Nothing else on the desk depends on them.

  • A submitted order is not a filled one. A limit entry may never fill, and the position does not exist until it does. The pilot agent is instructed never to describe one as the other.

  • Portfolio heat only sees the journal. A position taken without recording it is invisible to the risk checks, so the heat number is exposure as recorded rather than exposure as held. size_position says so in its limitations on every call.

  • 13Fs are long US equity only. No shorts, no cash, no bonds, no foreign listings. Portfolio weights are weights within the reported slice, so a manager described as "22% in Apple" is 22% of the part they had to disclose.

  • Congress coverage is House-only and partial. The Senate publishes separately and is not read. Only the reports actually opened are searched, so an absent ticker means "not in the reports read", never "not traded" — the response states how many of how many were parsed.

  • Congressional trades are parsed from PDF text, with no structured source behind them. The same caveat as filing text, one step weaker: follow the document link before quoting a specific transaction.

  • Forecasts assume volatility persists and drift is zero. Neither holds across an earnings date, and the bands are wrong in both directions over one. The bootstrap resamples days independently, so it reproduces fat tails but not volatility clustering — real drawdowns arrive in consecutive sessions more often than the model allows.

  • get_desk_health is offline. A reachable-but-broken API looks healthy, and a revoked credential passes a presence check.

Not financial advice

A research tool, not a prediction engine. /postmortem exists because calls need to be scored honestly rather than remembered selectively — and a good scoreboard is not a reason to trade larger. The limits do not move because recent results were pleasant.

Available Tools

27 tools
cancel_orderA

Cancel a working order.

Does not close a filled position — if the entry already filled, the
position remains open and must be closed explicitly.

Args:
    broker_order_id: Order id returned by `place_order`.

Inputs: broker_order_id (required). Returns: object

close_broker_positionA

Close an open position at market.

Closing at the broker does not close the thesis. Follow this with
`close_thesis` and the fill price so realised R is recorded.

Args:
    symbol: Stock symbol of the position to close.

Inputs: symbol (required). Returns: object

close_thesisA

Close a thesis, recording how it actually resolved.

Realised R is computed from the recorded entry and stop, so the result is
comparable across positions of different sizes.

Args:
    thesis_id: Identifier returned by `journal_thesis`.
    outcome: One of "target_hit", "stopped_out", "closed_manual",
        "expired", "invalidated".
    exit_price: Fill price, where there was one.
    note: What actually happened, especially if the thesis was right for
        the wrong reason.

Inputs: outcome (required), thesis_id (required), note, exit_price. Returns: object

get_accountA

Paper account state: equity, cash, buying power, positions value.

Refuses to return anything for an account it cannot prove is a paper
account. Use the `equity` here as the input to `size_position` rather than
assuming a figure.

Inputs: no named parameters. Returns: object

get_broker_ordersA

Orders at the paper broker, newest first.

Args:
    status: "open", "closed" or "all".
    limit: Maximum orders to return.

Inputs: limit, status. Returns: object

get_broker_positionsA

Open positions at the paper broker, with unrealised P&L.

Inputs: no named parameters. Returns: object

get_filingsB

Recent SEC filings for a company, newest first.

Args:
    ticker: Stock symbol.
    forms: Optional filter, e.g. ["10-K", "8-K"]. Omit for all forms.
    limit: Maximum filings to return.

Inputs: ticker (required), forms, limit. Returns: object

get_filing_textA

The text of a filing as filed, markup stripped, in windows.

Use this when the numbers raise a question they cannot answer — a margin
that moved, a quarter that beat and sold off anyway, a net income figure
that outran operating income. `get_filings` gives you the date; this gives
you what the company actually said.

Large filings are returned in windows. `truncated` and `next_offset` tell
you whether there is more; a silently cut filing reads like a complete one.
Prefer `search_filing_text` when you know what you are looking for.

Args:
    ticker: Stock symbol.
    accession: Specific filing accession number. Takes precedence.
    form: Read the newest filing of this form instead, e.g. "10-Q".
    offset: Character offset to start from, for paging.
    max_chars: Characters to return, capped at 100,000.

Inputs: ticker (required), form, offset, accession, +1 more. Returns: object

get_financial_historyA

Historical series for one line item, oldest to newest.

Use this for growth rates and trends. Each point states its own fiscal
year and source filing, so a restated figure is visible as such.

Args:
    ticker: Stock symbol, e.g. "NVDA".
    line_item: One of the supported keys, e.g. "revenue", "net_income".
    period: "annual" or "quarterly".
    limit: Maximum number of periods to return.

Inputs: ticker (required), line_item (required), limit, period. Returns: object

get_financialsA

Latest full financial statement for a company, from SEC XBRL filings.

Returns every available line item (revenue, margins, cash flow, balance
sheet) with the concept, period and filing each value came from. Line
items a company does not report appear under `unavailable` rather than
causing an error.

Args:
    ticker: Stock symbol, e.g. "AAPL".
    period: "annual" or "quarterly".

Inputs: ticker (required), period. Returns: object

get_insider_activityA

Recent Form 4 insider transaction filings for a company.

Args:
    ticker: Stock symbol.
    limit: Maximum Form 4 filings to return.

Inputs: ticker (required), limit. Returns: object

get_macro_seriesA

One macro series in detail, with its 1-, 3- and 12-month changes.

Args:
    key: Series key, e.g. "treasury_10y", "core_cpi", "vix". Call
        `get_macro_snapshot` to see them all.

Inputs: key (required). Returns: object

get_macro_snapshotA

Current macro conditions from FRED: rates, curve, inflation, jobs, vol.

Every reading carries its FRED series id, observation date and 1-, 3- and
12-month changes, computed against the last real print rather than a
calendar date. Series that fail are listed under `unavailable` rather than
taking the whole snapshot down.

Rates and inflation recalled from memory are wrong by whole percentage
points. Always read them here.

Args:
    series: Optional subset, e.g. ["treasury_10y", "curve_10y_2y"]. Omit
        for all of them.

Inputs: series. Returns: object

get_market_moversA

Today's largest percentage gainers and losers.

A starting list, not a signal — most large single-day moves are news the
market has already priced.

Args:
    top: How many of each to return, capped at 50.

Inputs: top. Returns: object

get_metricsA

Derived financial metrics for a company: margins, returns, growth, leverage.

Computed from SEC filings rather than estimated. Covers gross/operating/net
margin, R&D intensity, free cash flow and FCF margin, cash conversion,
debt/equity, net debt, ROE and ROA (on average balances where available),
and YoY plus CAGR growth for revenue, net income and operating cash flow.

Each metric lists the line items it was computed from and the period it
covers. Prefer this over deriving ratios yourself from `get_financials`.

Args:
    ticker: Stock symbol, e.g. "AAPL".
    history_years: Periods to span when computing growth rates.

Inputs: ticker (required), history_years. Returns: object

get_most_activeA

The day's most heavily traded names.

Args:
    by: "volume" or "trades".
    top: How many to return, capped at 50.

Inputs: by, top. Returns: object

get_risk_policyA

The desk's standing risk limits, each with the reasoning behind it.

Quote these when explaining a veto, rather than paraphrasing them.

Inputs: no named parameters. Returns: object

get_technicalsA

Technical picture for a symbol: trend, momentum, volatility, key levels.

Computed from daily bars on the consolidated SIP tape, so volume is the
full market rather than a single venue. Returns moving-average structure,
RSI, ATR (with stop-distance guidance), relative strength versus a
benchmark, drawdown from the one-year high, and clustered support and
resistance annotated with what each level does at the current price.

Anything that could not be computed from the available history is null and
explained under `limitations` rather than approximated.

Args:
    ticker: Stock symbol, e.g. "NVDA".
    benchmark: Symbol for relative strength, default "SPY".
    lookback_days: Calendar days of history to pull. 500 gives a 200-day
        moving average enough trading sessions.

Inputs: ticker (required), benchmark, lookback_days. Returns: object

get_thesisA

One recorded thesis in full, including its evidence and falsifiers.

Args:
    thesis_id: Identifier returned by `journal_thesis`.

Inputs: thesis_id (required). Returns: object

journal_thesisA

Record a call so it can be scored later, and return the stored thesis.

Write this at the moment the call is made. Evidence and falsifiers cannot
be reconstructed honestly once the outcome is known, which is the whole
reason the journal exists.

Record `direction="watch"` when the conclusion was to take no position —
a watch call that would have worked is as informative as a trade that did.

Args:
    ticker: Stock symbol.
    thesis: The call, in plain language. What this is and what to do.
    falsifiers: Observations that would prove the thesis wrong — price
        levels, a filing, a metric crossing a threshold. Required.
    direction: "long", "short" or "watch".
    horizon: "swing", "positional" or "long_term".
    conviction: Free text, e.g. "low", "medium", "high".
    entry: Entry price. Required for a long or short.
    stop: Stop price. Required for a long or short.
    target: Target price.
    reward_risk: Reward:risk, as reported by `size_position`.
    shares: Share count, as sized by `size_position`.
    dollar_risk: Capital at risk, as sized by `size_position`.
    risk_verdict: The `verdict` returned by `size_position`.
    evidence: Supporting figures, each ideally {"claim", "source",
        "period"}, so the thesis can be audited against the filings.
    gaps: Data that was unavailable, stale or unresolved at the time.

Inputs: thesis (required), ticker (required), falsifiers (required), gaps, +11 more. Returns: object

list_line_itemsC

Financial line items this server can resolve.

Inputs: no named parameters. Returns: object

list_thesesB

Recorded theses, newest first.

Args:
    ticker: Optional symbol filter.
    status: Optional "open" or "closed" filter.
    limit: Maximum theses to return.

Inputs: limit, status, ticker. Returns: object

place_orderA

Send a journalled, risk-approved thesis to the paper broker.

Takes a thesis id and nothing else, by design. Symbol, share count, entry,
stop and target all come from the journal entry, which exists only because
the risk officer approved it. There is no way to place an order this desk
did not size.

Refuses when: the thesis is closed, is a watch call, carries a vetoed risk
verdict, was sized at zero shares, lacks an entry or stop, or already has
an order attached. Orders go out as brackets, so the stop is submitted with
the entry rather than left to a later call.

A submitted order is not a filled one. Check `get_broker_orders`.

Args:
    thesis_id: Identifier returned by `journal_thesis`.

Inputs: thesis_id (required). Returns: object

rank_candidatesA

Order a candidate list by relative strength, with supporting measures.

Turns a raw list into a shortlist worth spending the research agents on.
Returns trend structure, distance from the 50-day, RSI, ATR as a percentage
of price and drawdown from the one-year high for each name. Symbols whose
history could not be retrieved appear under `unavailable` rather than
quietly dropping out.

This orders candidates; it does not judge them.

Args:
    symbols: Up to 40 symbols. Each costs a separate history request.
    benchmark: Symbol for relative strength, default "SPY".
    lookback_days: Calendar days of history per symbol.

Inputs: symbols (required), benchmark, lookback_days. Returns: object

reconcile_positionsA

Compare broker positions against open journalled theses.

Portfolio heat is computed from the journal, so a position held without a
thesis is exposure the risk checks cannot see. This reports both kinds of
mismatch: untracked positions, and theses whose entry never filled.

Inputs: no named parameters. Returns: object

search_filing_textA

Passages of a filing around every occurrence of a term, verbatim.

Far cheaper than paging a 10-K to find the one paragraph that explains a
tax benefit or a goodwill charge. No match means the term is absent from
this document, not that the fact is absent from the company's filings.

Args:
    ticker: Stock symbol.
    query: Term to find, e.g. "stock-based compensation".
    accession: Specific filing accession number. Takes precedence.
    form: Search the newest filing of this form instead, e.g. "10-K".
    context: Characters of surrounding text per hit.
    max_hits: Maximum passages to return.

Inputs: query (required), ticker (required), form, context, +2 more. Returns: object

size_positionA

Size a trade against the desk's risk limits and rule on whether it passes.

Returns the share count, the capital genuinely at risk, current portfolio
heat from open journalled theses, and every limit checked with its observed
value and threshold. The `verdict` is one of `approved`,
`approved_with_warnings` or `vetoed`; a veto is a refusal, not a preference.

Never work a share count out yourself — this is where a correct thesis
loses money. Anything that could not be checked, such as a missing ATR or
target, appears under `limitations` rather than being assumed to pass.

Args:
    ticker: Stock symbol, e.g. "NVDA".
    direction: "long" or "short".
    entry: Intended entry price.
    stop: Stop price. Must sit below entry for a long, above for a short.
    account_equity: Total account equity the risk budget is drawn from.
    target: Intended exit. Omit only if the chartist gave none.
    atr: ATR(14) from `get_technicals`, to check the stop against noise.
    risk_pct: Requested % of equity at risk. Clamped to the policy maximum.

Inputs: stop (required), entry (required), ticker (required), direction (required), +4 more. Returns: object

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A3.7/5.0

    Scored across 27 tools

    Disambiguation5/5

    Each tool targets a distinct resource or action. Similar-sounding tools like get_financials, get_financial_history, and get_metrics have clearly separated purposes (full statement, single line-item series, derived ratios), and research vs. trading vs. journaling groups are unmistakable.

    Naming Consistency5/5

    All tool names follow the verb_noun snake_case pattern (get_risk_policy, list_theses, place_order, reconcile_positions, close_thesis, etc.). Verb prefixes vary by operation but are consistently used, with no mixing of camelCase or other conventions.

    Tool Count4/5

    At 27 tools, this is slightly over the 25-tool threshold, but the server covers a broad domain: market data, SEC filings, macro data, technical analysis, risk sizing, journaling, and broker integration. Each tool earns its place with a distinct function; no obvious redundancy.

    Completeness4/5

    The tool set provides a complete research-to-execution pipeline: gather data, size positions, journal theses, place orders, monitor positions, and close trades. Small gaps like lacking an explicit update-thesis or order-modification tool are acceptable because close/cancel and re-journaling cover those cases.

    Maintenance

    ActivityInactive
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      C
      quality
      D
      maintenance
      Provides AI agents with institutional-grade quantitative finance tools including real-time market data, paper trading via Alpaca, risk analysis with Monte Carlo simulations, backtesting, and multi-source news sentiment analysis for portfolio management and trading strategy development.
      31
      5
      MIT
    • A
      license
      Not graded
      quality
      B
      maintenance
      Enables AI agents to execute stock trading operations with built-in risk controls and human approval workflows. Supports paper trading simulation, real brokerage integration (Alpaca, Tradier), backtesting, sentiment analysis, and portfolio management while maintaining strict separation between AI intelligence and trade execution.
      MIT
    • A
      license
      Not graded
      quality
      D
      maintenance
      Enables AI agents to perform grounded equity research by analyzing tickers from SEC filings and market data, producing citation-guarded memos with pre-computed fundamentals.
      MIT
    • A
      license
      Not graded
      quality
      C
      maintenance
      Enables AI agents to propose stock, ETF, and crypto trades through Alpaca paper trading while enforcing deterministic policy rules and recording every decision in an audit trail.
      8 npm
      MIT