OpenInvest
Server Configuration
Describes the environment variables required to run the server.
| Name | Required | Description | Default |
|---|---|---|---|
| LLM_MODEL | No | Model ID (default: deepseek-chat) | deepseek-chat |
| INVEST_HOME | Yes | Path to the data directory (e.g., ~/openInvest) | |
| LLM_API_KEY | Yes | API key for the LLM provider (required for automated decisions) | |
| LLM_BASE_URL | No | Base URL for the LLM API (default: https://api.deepseek.com) | https://api.deepseek.com |
Instructions
Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.
This server publishes no instructions, or was last inspected before Glama recorded them.
Capabilities
Features and capabilities supported by this server
Protocol revision2025-11-25
| Capability | Details |
|---|---|
| tools | {
"listChanged": false
} |
| prompts | {
"listChanged": false
} |
| resources | {
"subscribe": false,
"listChanged": false
} |
| experimental | {} |
Tools
Functions exposed to the LLM to take actions
| Name | Description |
|---|---|
| statusA | Get a full snapshot of the user's portfolio: cash balances per currency, every holding with units / average cost / live price, and unrealized P&L per position and in total. Use when the user asks "show my portfolio", "how is my P&L", or before proposing any trade. Read-only; fetches live quotes, so values change between calls. Returns:
Object with |
| doctorA | Health check, the first call of a session (same output as
Read-only; makes one short HTTP probe to the configured LLM endpoint when a key is set. Returns:
Object with |
| strategyA | Get the user's investment strategy: target stock/cash allocation, the list of tracked assets (per-asset investment cap, purchase channel, fee settings), and long-term insights distilled by the nightly Dreaming memory-consolidation job. Use when deciding whether a proposed trade fits the user's plan, or when the user asks "what is my strategy / what am I tracking". Read-only. Returns:
Object with |
| historyA | Get the most recent trade records and committee verdict history. Use when the user asks "what did I buy recently" or "what did the committee decide lately". Read-only. Args: n: Maximum number of recent trades to return (default 10). Returns:
Object with |
| live_pricesA | Fetch a one-shot market backdrop: spot gold (USD/oz and CNY/gram),
USDCNY and AUDCNY FX rates, the NDQ.AX ETF price, the VIX volatility
index, and the 10-year US Treasury yield (TNX). A fixed set of reference
instruments, independent of what the user holds (use Use for quick market context before analysis or when the user asks "how is the market / what's the gold price". Read-only; single batch, no arguments. Returns:
Object keyed by instrument (GC_F_usd_per_oz, gold_cny_per_gram_spot,
USDCNY, AUDCNY, NDQ_AX, VIX, TNX) plus |
| what_ifA | Simulate portfolio P&L for a hypothetical price move: "what happens to my portfolio if moves ±pct% / reaches ". Pure arithmetic over current holdings — no LLM call, instant, free. Use when the user asks scenario questions like "if the Nasdaq drops 10%,
how much do I lose". Provide exactly one of Args: symbol: yfinance ticker held or tracked by the user (e.g. "NDQ.AX", "GC=F", "510300.SS"). pct: Hypothetical percent change, e.g. -10 for a 10% drop. price: Hypothetical absolute target price (alternative to pct). Returns: Object with the position's simulated value change and the resulting portfolio-level P&L delta. |
| rebalance_planA | Compute a rebalance list against the target allocation in strategy.md: what to BUY / SELL, how much, in whole lots, and why. Only produces the list, places no order — the human executes. No LLM call. Use when the user asks "do I need to rebalance", "how far am I from my
target allocation" or "what should I buy/sell to get back on target".
Per-asset targets come from Args: mode: "cash_only" | "sell_to_rebalance" | "hybrid" (default). band_pct: Drift band in percentage points, default 5. Returns:
|
| disciplineA | Get the committee's discipline ledger: how often it chose inaction (HOLD ratio), how many impulsive user trades its rules intercepted, and the counterfactual money saved/lost by those interventions (ADR-023: the system's proven value is discipline and transparency, not alpha). Use when the user asks "what has the committee blocked" or "is this tool actually helping". Read-only. Returns:
Object with |
| decisionsA | Get the unified decision ledger: every committee verdict joined with rule interventions, the user's actual executions or refusals (with reasons), and post-hoc outcome data — plus an adoption-rate summary. Answers "how often did I follow the advice", "which recommendations did
I skip", and "what did the safety rules rewrite". Read-only. Filter by
Args: days: Look-back window in days (default 90). symbol: Only decisions on this ticker (case-insensitive). verdict: Only decisions with this verdict. limit: Max decisions listed (default 20; 0 = all). Returns:
Object with |
| explain_decisionA | Get the full reasoning behind one committee verdict: the complete 4-role debate transcript with the CIO memo, plus the path-probability snapshot the CIO saw at decision time. Use when the user asks "why was today's verdict HOLD" or wants to audit a past decision. Read-only. Args:
decision_id: "/", e.g. "2026-07-03/GC=F" — exactly as
returned in the Returns:
Object with verdict, confidence (the CIO's self-reported number, kept
for the record), |
| record_executionA | Record whether the user executed or declined a committee verdict, with their reason. Appends to the execution ledger; idempotent — replaying the same record is a no-op, so retries are safe. Call when the user says "I bought it / I didn't buy / I disagree". When
they decline, ask one short question for the reason first — this closes
the adoption-rate loop that Args:
decision_id: "/" from the Returns: The stored execution record, or {"status": "error", "error": ...}. |
| ingest_eventA | Feed a finance news item you (the host agent) found into the event ledger, graded and mapped to affected symbols, stored for committee RAG recall. Preferred: normalize it yourself — pass You have far better search reach than the self-hosted crawler (including Chinese-language sources) — proactively feed news relevant to the user's holdings, especially A-share/regional coverage the crawler misses. Idempotent: re-sending the same url or claim does not double-insert. Requires a backend LLM key. Args:
title: Headline of the news item.
url: Canonical source URL (also the dedup key).
snippet: Short excerpt or summary of the article body.
source: Publisher name (e.g. "Reuters") — the news outlet.
published_at: ISO 8601 publication time, if known.
ingested_by: Your own agent identity (e.g. "hermes") for provenance;
distinct in meaning from Returns:
Ingestion result with the normalized event id(s), dedup status, and
|
| pending_newsA | News the backend fetched but did not normalize because reasoning is
delegated to you (agent mode). For each relevant item call |
| daily_reportA | Assemble today's daily report (portfolio, verdicts, performance,
holdings digest) and return it as Agent mode (keyless backend): run prepare_committee → roles →
submit_committee for every target asset FIRST; this tool then only reads
today's submitted verdicts — nothing is re-run by a backend model. Assets
without a submitted verdict are listed in |
| pending_committeesA | Committee re-runs that events or price moves requested while reasoning
is delegated to you (agent mode). For each item run |
| news_sourcesA | List the news feed sources the crawler pulls from: the built-in default feeds plus user-added extra feeds. Extra feeds can be added/removed with add_news_source / remove_news_source; defaults are fixed. Use when someone asks "what news sources do you follow" or before adding/removing a source. |
| add_news_sourceA | Add an RSS/Atom feed to the crawler's source list. The URL must be http(s) on a public host (fetched directly, not via HTTP(S)_PROXY), and is live-probed before saving — a URL that doesn't parse as a feed is rejected. Idempotent: re-adding an existing URL returns the existing entry. Capped so the list can't grow unbounded. Use when someone says "follow 's news" / "加个新闻源". |
| remove_news_sourceA | Remove a user-added news feed by name or URL. Built-in default feeds cannot be removed. |
| buyA | Record a buy in the local ledger: adds to an existing position with weighted-average cost, or opens a new position for an unseen symbol. This bookkeeps a trade the user already placed with their broker — openInvest never places real orders. Confirm symbol, units, and price with the user before calling; this
moves ledger cash. A position the user already held before using
openInvest is not a purchase: record it with Args:
symbol: yfinance ticker (e.g. "AAPL", "510300.SS"). Gold = "GC=F", booked in
CNY per gram only (units = grams, currency "CNY", kind "metal", unit_label "克").
units: Quantity bought; must be > 0.
price: Execution price per unit, in Returns: Updated position summary, or {"status": "error", "error": ...}. |
| record_existing_positionA | Record a position the user already held before using openInvest
(onboarding backfill) WITHOUT touching ledger cash. History is marked
Only for positions that Args:
symbol: yfinance ticker (e.g. "AAPL", "510300.SS"). Gold = "GC=F", booked in
CNY per gram only (units = grams, currency "CNY", kind "metal", unit_label "克").
units: Quantity held; must be > 0.
price: Average cost per unit, in Returns: Updated position summary, or {"status": "error", "error": ...}. |
| sellA | Record a sell in the local ledger: reduces the position's units (average cost unchanged) and credits cash in the holding's cost currency. Bookkeeps a trade already executed at the user's broker — openInvest never places real orders. Confirm symbol, units, and price with the user before calling; this moves ledger cash. Args: symbol: yfinance ticker of an existing holding. units: Quantity sold; must be > 0. price: Execution price per unit, in the holding's cost currency. idempotency_key: Reuse the same key when retrying this exact call; recorded once. Returns: Updated position summary, or {"status": "error", "error": ...}. |
| depositA | Record a cash deposit into the ledger, in any currency. Bookkeeping only — no real payment system is connected. Args: currency: ISO-style currency code, e.g. "CNY", "USD", "AUD". amount: Amount to add; must be > 0. idempotency_key: Reuse the same key when retrying this exact call; recorded once. Returns: Updated cash balances, or {"status": "error", "error": ...}. |
| withdrawA | Record a cash withdrawal from the ledger, in any currency. Fails if the balance is insufficient. Bookkeeping only — no real payment system is connected. Args: currency: ISO-style currency code, e.g. "CNY", "USD", "AUD". amount: Amount to remove; must be > 0. idempotency_key: Reuse the same key when retrying this exact call; recorded once. Returns: Updated cash balances, or {"status": "error", "error": ...}. |
| record_dividendA | Record a dividend (or interest) the user already received: credits ledger cash by amount - tax_withheld, positions unchanged. It counts as account return (inside NAV), not as a deposit. Bookkeeping only — openInvest never moves money. Confirm symbol, gross amount, withholding tax, and the date with the user before calling; this moves ledger cash. A symbol that is neither held nor in the ledger is rejected (typo guard). Args:
amount: Gross amount before withholding tax; must be > 0.
currency: Currency of the cash credited.
symbol: Ticker (required for dividends; optional for interest).
kind: "dividend" (default) or "interest".
tax_withheld: Tax withheld at source; credited cash = amount - tax_withheld.
reinvest: DRIP — buy more Returns: Cash credited and new balance, or {"status": "error", "error": ...}. |
| record_feeA | Record a fee or a tax the user already paid: debits ledger cash, positions unchanged. It is an account cost (inside NAV), not a withdrawal. Trade commissions need no separate entry — buy / sell already book the trade. Fails if the currency's cash balance is lower than the amount. Confirm the amount and date with the user before calling; this moves ledger cash. Args: amount: Amount paid; must be > 0. currency: Currency of the cash debited. kind: "fee" (default) or "tax". symbol: Related ticker (optional). trade_date: YYYY-MM-DD it was charged (default today). note: Free-text note. idempotency_key: Reuse the same key when retrying this exact call; recorded once. Returns: Cash debited and new balance, or {"status": "error", "error": ...}. |
| record_splitA | Record a stock split / reverse split: units x ratio, average cost / ratio, cash unchanged (market value is unchanged). Call it once per split — a second call multiplies the position again; use an idempotency_key if unsure whether an earlier attempt landed. Rejected when the ledger already has trades / corrections for the symbol on or after trade_date (their units are already post-split, so multiplying the whole snapshot would invent shares) — pass ledger_only=true if the snapshot is already correct. Confirm symbol, ratio, and ex-date with the user before calling; this rewrites the position's units and average cost. Args: symbol: Ticker that split. ratio: New / old shares (10-for-1 = 10; 1-for-10 reverse = 0.1). trade_date: Ex-date YYYY-MM-DD (default today). note: Free-text note. ledger_only: Write the ledger row only; do not change units / average cost. idempotency_key: Reuse the same key when retrying this exact call; recorded once. Returns:
New units / average cost (plus a |
| record_return_of_capitalA | Record a return of capital (some ETF / REIT distributions): credits ledger cash, lowers the holding's average cost by amount / units (a cost reduction, not income), units unchanged. Cost floors at 0; any excess is reported as excess_over_cost (treat as a capital gain). Confirm symbol, amount, and date with the user before calling; this moves ledger cash and changes average cost. Args: symbol: Held ticker. amount: Cash received; must be > 0. currency: Currency of the cash (must match the holding's cost currency). trade_date: YYYY-MM-DD received (default today). note: Free-text note. idempotency_key: Reuse the same key when retrying this exact call; recorded once. Returns: New average cost, or {"status": "error", "error": ...}. |
| place_fund_orderA | Record the first leg of an off-exchange fund purchase the user already
placed with the fund platform: cash - amount, and the order is tracked as
in transit ( The expected confirm date uses a Monday-Friday calendar (no China holiday
table) and a lag by fund type (QDII / FOF are slower, read from the fund's
profile); the result says so ( Confirm fund, amount, and order time with the user before calling; this moves ledger cash. Args: symbol: FUND:<6 digits>. amount: Subscription amount in CNY; must be > 0. currency: Currency of the amount (CNY). order_ts: Order time, ISO 8601 Beijing time (default now). note: Free-text note. idempotency_key: Reuse the same key when retrying this exact call; recorded once. Returns:
order_id, pricing day ( |
| confirm_fund_orderA | Second leg of an off-exchange fund purchase: turn an in-transit order
into shares at the pricing day's NAV (units = (amount - fee) / nav; average
cost updates, cash unchanged). The Safe to call twice: an order that is already confirmed returns {"status": "already_confirmed"} (a cancelled one {"status": "cancelled"}) and nothing changes. If the pricing-day NAV is not published yet the result is {"status": "pending_nav"} and the order stays in transit. Args: order_id: The order to confirm. nav: Unit NAV; omit to use the published NAV of the pricing day. fee: Subscription fee in CNY (default 0). Returns: Units added, NAV used and new average cost, or {"status": "error", "error": ...}. |
| cancel_fund_orderA | Cancel an in-transit off-exchange fund order that was never executed
(the user withdrew it on the fund platform): the amount returns to ledger
cash. Only orders still in transit can be cancelled — a confirmed order
already holds shares (use Confirm with the user before calling; this moves ledger cash. Args: order_id: The in-transit order to cancel. Returns: Cash returned and new balance, or {"status": "error", "error": ...}. |
| set_allocationsA | Update the strategy's target allocation: the stock/cash ratio and/or
the per-asset target weights that Use when the user says e.g. "set my target to 70% stock / 30% cash" or "put 30% in 510300.SS and 20% in gold". Args: target_allocation_stock: Stock weight in [0, 1], e.g. 0.7 (with cash). target_allocation_cash: Cash weight in [0, 1], e.g. 0.3 (with stock). target_pcts: {symbol: weight in [0, 1]} per tracked asset; null clears. alerts: {symbol: {rule: value}} alert lines per tracked asset; null clears a rule (or, for a whole symbol, all its rules). Returns: The updated allocation and target assets, or {"status": "error", "error": ...}. |
| track_assetA | Add a symbol to the tracked-asset list, or update an existing entry (idempotent upsert: only the fields you pass are changed). The tracked list decides which symbols the committee and DCA jobs cover. The alert_* parameters set per-asset alert lines the price sentinel checks every few minutes (price lines: 24h cooldown; day-move lines: once per trading day); each one you pass overwrites that line only. To clear a line use set_allocations with a null value. Delivery needs INVEST_EVENT_ALERT=1 on the server (default off — without it a hit is only logged to the event store, nothing is sent). Use when the user says "track AAPL", wants to change a tracked asset's cap/channel/fees/target weight, or says "alert me when X drops below N" / "tell me if it falls 3% in a day". Args: symbol: yfinance ticker to track (e.g. "AAPL", "0700.HK", "BTC-USD"). max_single_invest_cny: Per-decision investment cap in CNY. On a new entry it defaults to the strategy-level max_single_invest_cny; optional on update. display_name: Human-friendly name shown in reports. channel: Where the user actually buys it (broker/app name). price_offset_pct: Systematic offset between the quote and the user's actual fill price, in percent (e.g. bank gold spread). sell_fee_pct: Sell-side fee in percent, used by fee-aware math. target_pct: Target weight as a fraction of total assets in [0, 1] (read by rebalance_plan); all tracked assets' weights must sum to <= target_allocation_stock or the write is rejected. alert_price_above / alert_price_below: price lines (status.current_price basis). alert_day_drop_pct / alert_day_rise_pct: the latest daily bar's move vs the previous close, percentage points. alert_drawdown_from_cost_pct: held position below average cost, percentage points. Returns: The stored asset entry, or {"status": "error", "error": ...}. |
| untrack_assetA | Remove a symbol from the tracked-asset list — the committee and DCA jobs stop covering it. Holdings and trade history are untouched; schema validation guarantees at least one tracked asset remains. Args: symbol: yfinance ticker currently in the tracked list. Returns: The updated tracked list, or {"status": "error", "error": ...}. |
| prepare_committeeA | Step 1 of the agent-run committee (Coordinator path): get everything YOU need to play the 4 roles yourself — no backend LLM key involved. Returns the deterministic data blocks (regime, sentiment / defense flag,
valuation, macro, events, portfolio, path reference) plus the 6 role
prompts and The backend keeps its own copy of the deterministic facts from this call and uses them when you submit (defense downgrade, regime, path snapshot), so a fact you forget to paste cannot silently switch a rule off. Submit within 6 hours of preparing. |
| submit_committeeA | Step 2 of the agent-run committee: hand in the transcript you produced
from If the CIO section has no parseable verdict, nothing is saved and this returns an error — fix the CIO section and submit again. Returns:
|
| run_committeeA | Run the 4-role LLM investment committee on a symbol (Direct path): Macro Strategist, Quant Analyst, and Risk Officer debate from isolated evidence, then a CIO synthesizes one calibrated BUY/HOLD/SELL-style verdict with a written memo. Requires a backend LLM key (e.g. DEEPSEEK_API_KEY) and takes 30-90s on
a cache miss. If the symbol was already analyzed today, the cached
verdict is returned instantly unless Args: symbol: Any yfinance ticker (US / HK / A-share / ETF / crypto / commodities), e.g. "AAPL", "GC=F", "510300.SS". force: Re-run even if a verdict already exists for today. max_rounds: Cross-challenge debate rounds (default 1). Returns:
Object with |
Prompts
Interactive templates invoked by user choice
| Name | Description |
|---|---|
No prompts | |
Resources
Contextual data attached and managed by the client
| Name | Description |
|---|---|
No resources | |
TDQS
Scored across 36 tools
Most tools have clearly distinct purposes, well separated by descriptions: the ledger ops (buy, sell, record_dividend, record_split, etc.), the committee paths (prepare/submit/run), and analysis tools (status, rebalance_plan, what_if) do not collide. The main soft overlap is among read-ledger tools (history, decisions, discipline, explain_decision) and between strategy vs set_allocations vs status, but the descriptions differentiate them adequately.
The majority follow a predictable snake_case verb_noun or resource pattern (add_news_source, remove_news_source, record_fee, set_allocations, track_asset, untrack_asset, record_split). Read-only tools are bare nouns (strategy, history, status, doctor, live_prices), which is a minor but readable deviation rather than a chaotic mix.
36 tools is heavy and above the comfortable 3-15 band, but the surface legitimately spans several distinct subdomains (portfolio ledger, fund orders, committee workflow, news pipeline, strategy config, reporting), so each tool roughly earns its place rather than being redundant.
The surface is remarkably complete: full portfolio lifecycle (buy/sell/deposit/withdraw/dividend/split/fee/ROC/fund orders), strategy configuration (set_allocations, track/untrack, read strategy), committee end-to-end (prepare/submit/run/decisions/explain/record_execution), and news ingestion. No obvious dead ends for the stated investment-assistant purpose.