Call this server's tools from your browser. Nothing to install.
https://mcp.twmarketdata.com/mcp
TWMD API key (get one at https://twmarketdata.com). A key-free trial tier is available for sample tickers.
Requests go through mcptry because browsers can't call most MCP servers directly. Header values are sent to the server for that request only and are never stored or logged.
Tool list from our last check. Connect live to refresh it.
agent_activity Read-only
What YOUR agents have actually done, from the durable audit trail.
Every resident agent records what it did and ON WHAT BASIS — the rule and the two closes
behind an alert, the coverage and limits behind a risk finding, the run_id and declared trial
count behind a factor verdict. This is that trail, and it survives deploys.
Reports which agents have recorded NOTHING (`coverage.missing`), because "the monitor has
been quiet" and "the monitor is not running" look identical from the records alone and only
one of them means your alerts work.
Args:
limit: how many records to return, newest first (max 200).
Tools (41)
agent_activity
What YOUR agents have actually done, from the durable audit trail.
Every resident agent records what it did and ON WHAT BASIS — the rule and the two closes
behind an alert, the coverage and limits behind a risk finding, the run_id and declared trial
count behind a factor verdict. This is that trail, and it survives deploys.
Reports which agents have recorded NOTHING (`coverage.missing`), because "the monitor has
been quiet" and "the monitor is not running" look identical from the records alone and only
one of them means your alerts work.
Args:
limit: how many records to return, newest first (max 200).
Record a HUMAN's approval of a proposed action.
This writes an audit record naming who approved what, and when. It does NOT execute the
action: TWMD has no order or funds path, by design. Execution, if any, happens elsewhere and
is performed by a person.
Args:
action_id: from `list_pending_actions` or a report's `proposed_actions`.
approver: who is approving. Required — an unattributed approval is not an approval.
Answer a plain-language question in Taiwanese-market vocabulary, sentence by sourced sentence.
★ FOR BEGINNERS WHO DO NOT KNOW WHICH DATASET THEY WANT. Ask "PBR 是什麼" or "什麼叫漲跌停"
in ordinary words; routing happens on our side. `describe_dataset` explains a table you already
named, `search_filings` digs through company disclosures, and `query_dataset` returns rows —
this one turns a beginner's wording into a sourced explanation instead.
★ EVERY SENTENCE CARRIES A CITATION OR IS NOT RETURNED. Vocabulary sentences cite
`glossary:<id>`. Figures cite the dataset row they came from. A sentence whose number cannot be
traced to a retrieved row is DROPPED — it appears in `dropped[]` with status `unverifiable`, and
never in `claims[]`. There is no path by which this tool composes a number from its own memory.
★ TERMS OUTSIDE THE GLOSSARY RETURN `unsupported_term`, NOT A GUESS. The corpus is 99 curated
Taiwan-market terms. CAPM, options greeks and general finance vocabulary are not in it, and the
honest answer is that we do not cover them.
⚠️ Explanations only. It states what a term means and what a figure was; it does not tell you
what to do about either. Args: question, optional as_of (YYYY-MM-DD).
Sort corporate dates into what is still ahead and what has already passed.
★ TWO DATES, NOT ONE. What is "upcoming" is decided by the date the event HAPPENS; `as_of`
filters on the date it was ANNOUNCED. An ex-dividend declared on 2026-08-01 for 2026-09-15 is
both already known and still ahead on 2026-08-10. Collapsing the two fields either hides every
future date or reports last month's ex-dividend as though it were coming.
★ ELAPSED DATES ARE SEPARATED, NOT DISCARDED. They come back under `past` — the previous
ex-dividend is useful context for a question about the next one — but they can never appear
under `upcoming`.
★ NOT `search_filings` AND NOT `query_dataset`. Those retrieve disclosures and rows; this one
only arranges dated corporate events on a timeline relative to now.
⚠️ A scheduled date is a schedule, not a promise; companies move them.
Args: rows (dated events), today (YYYY-MM-DD), optional as_of.
Turn rows you already fetched into a Vega-Lite drawing your chat window can render.
★ IT DRAWS; IT DOES NOT FETCH. Hand it the output of `query_dataset` — this tool never touches
the database, so it cannot bypass the `as_of` filter those rows were selected under. A plotting
tool that fetched its own numbers would be a second data path, and a second path eventually
disagrees with the first about what was knowable when.
★ GAPS BREAK THE LINE INSTEAD OF BEING BRIDGED. A missing value is emitted as null, so the
rendered line stops rather than sloping smoothly across a period where nothing was published.
The absent positions are also listed in `data_gaps`, because a break is easy to misread as a
flat stretch. Zero is never substituted — zero draws a real low point for something that never
happened.
★ THE DRAWING POINTS BACK AT THE PROOF. `citation` and `inclusion_pointer` travel with the
spec, so a picture and the rows behind it name the same published checkpoint.
⚠️ Rendering, not analysis. A shape you notice in a picture is not a forecast.
Args: rows, x_field, y_field, optional title and mark (line/point/bar/area).
read-only
Argument
Type
Description
mark
string
rows*
array
title
string
x_field*
string
y_field*
string
cite_this
Produce a bibliographic citation for TWMD data — APA, BibTeX, and a re-verifiable token.
★ FOR PAPERS, REGULATORY FILINGS AND ANYTHING A REVIEWER WILL RE-CHECK LATER. A dataset is
corrected, backfilled and re-run. Three years from now a reviewer opening our API sees different
numbers than the
paper, and nobody — author, reviewer, or us — can tell whether the data changed or the author
mis-transcribed. So the citation carries `as_of`, the Merkle `checkpoint_root`, and a
`verify_url`, plus a signed `reproducible_token` that binds those fields TO EACH OTHER.
★ PASS `as_of` IF YOUR WORK IS POINT-IN-TIME. Without it a citation is still produced, but it is
marked `point_in_time: false` and the APA line reads "Retrieved <date>" instead of naming a
knowledge horizon — because re-running the same query later can legitimately return different
figures, and nothing in the data would mark the difference.
★ WHAT IT PROVES: which data was used, and that those fields were signed by us. **NOT that the
figures are correct** — a faithfully committed wrong figure cites and verifies exactly like a
right one. `limitations` says so in the response; keep it when you quote the citation.
Args:
dataset: the dataset id you queried, e.g. 'valuation'.
as_of: the knowledge cutoff your work used (YYYY-MM-DD). Omit only for present-day lookups.
row_key: pin the citation to one specific row, if you are citing one row.
Lay two to five named companies side by side on the same measures, gaps marked as gaps.
★ AN ABSENT FIGURE STAYS ABSENT, AND THE COMPANY STAYS ON THE TABLE. A blank cell is reported
as `available: false`, never filled with a zero, a previous period, or by quietly dropping the
column. Dropping is the worst of the three: it converts "we do not hold this measure for that
company" into "you did not ask about that company".
★ EACH CELL NAMES ITS OWN SOURCE. Margins and institutional flows come from different datasets,
so one citation for the whole table would imply every figure came from one query.
★ NOT `screen` AND NOT `find_related`. `screen` finds symbols from a description when you have
none; `find_related` walks supply-chain links. This one needs you to already know which two to
five companies you mean.
⚠️ Placing figures next to each other is not ranking them. A measure one company publishes and
another does not is a coverage difference, not evidence about either.
Args: tickers (2-5), metrics, optional pre-fetched rows_by_ticker, optional as_of.
Cancel one armed price trigger permanently, by its rule id.
Disarms a single watch so it will not fire again — the opposite of `set_price_alert`,
and unlike `list_alerts` it changes state rather than reporting it. Cancellation is
irreversible: re-arming means creating a fresh trigger. Naming somebody else's rule id
cancels nothing at all. Args: rule_id.
FULL semantics of one dataset: grain (what a row is), field meanings+units, ★TIME-CORRECTNESS
rules (knowledge_time_field / point_in_time_safe — read before backtesting), relations for
cross-table reasoning, agent_hints (when to use), quant_use (which factors). Args: dataset_id.
Where did this number come from, and when could anyone have known it? (WP-6)
★ THIS IS THE GROUNDING TOOL. Every other tool's answer is supposed to be expandable through
this one: given a ticker, a field and a date, it names the dataset that serves that field, the
knowledge column, and WHICH as_of rule applies on that date. Use it whenever you are about to
state a number as fact.
★ IT DOES NOT OVERLAP WITH THE OTHER TWO PROVENANCE TOOLS, and they are not substitutes:
`get_inclusion_proof(dataset, row_key)` proves a ROW was in a published snapshot (Merkle).
`cite_this(dataset, as_of)` produces a citation for a DATASET.
`explain_provenance(ticker, field, date)` answers WHERE A CELL came from and WHEN it was
knowable. Nothing else answered that.
★ FOUR VERDICTS, and `partial` is not `resolved`:
resolved dataset, knowledge axis, as_of rule AND the official endpoint.
partial ⚠️ the first three, but NOT the official endpoint — no dataset declares a
per-cell endpoint today. "When was it knowable" and "which official
endpoint produced it" are different questions; conflating them is exactly
the vagueness this tool exists to remove.
ambiguous_field ⚠️ the field lives in several datasets. Every candidate is listed and NONE
is picked for you: picking one would hand you a confident wrong answer.
Re-ask with `dataset=`.
unknown_field no served dataset declares that field. This is "we do not have that
column", NOT "no data found" — a different next step for you.
★ READ `limitations`. It says when the knowability date is DERIVED (statutory deadline, a
conservative upper bound) rather than observed, and whether the rule came from declarations or
from the live schema. A declared capability is not the same as a working one.
Args:
ticker: the security, e.g. '2330'.
field: the column you are about to quote, e.g. 'close', 'revenue'.
date: the date your claim is about (YYYY-MM-DD).
dataset: pin the answer to one dataset when the field is ambiguous.
read-only
Argument
Type
find_related
Traverse the knowledge graph for cross-table / supply-chain reasoning.
- dataset_id: returns join-able datasets (+why) to plan multi-table analysis.
- ticker: returns its industry value-chain node + peers in the same node (supply-chain reasoning).
Args: dataset_id (e.g. 'equity_daily_prices') and/or ticker (e.g. '2330').
Retrieve a previous backtest by run_id — the full record, including why it was rejected.
Only runs in YOUR namespace are visible; a run_id belonging to someone else is simply not
found. Args: run_id (the `twmd_bt_…` handle returned by run_backtest).
Emit a copy-pasteable HTTP snippet wired to the real endpoint, header and parameter names.
★ FOR WRITING YOUR OWN CLIENT, NOT FOR GETTING DATA. Every other tool here answers a question;
this one hands you source code so your program can ask it directly over HTTPS. Nothing is
fetched and no rows come back.
★ THE CONSTANTS ARE READ OUT OF THE SERVER, NOT REMEMBERED. Base URL, the `X-API-Key` header
spelling, and the route path all come from the code that serves them. The worst kind of broken
example is one that looks right: a mistyped path 404s and the reader blames their own key.
★ AN INTENT IT HAS NO ROUTE FOR IS REFUSED. It will not point at a plausible-looking path it
has not confirmed. Python output is passed through Python's own compiler before it is returned;
the JavaScript variant is structure-checked only, and says so.
⚠️ The credential in the snippet is an obvious placeholder, never a realistic-looking string —
a convincing fake gets pasted, sent, and then fails somewhere nobody can trace.
Args: intent, language (python/javascript), ticker, optional as_of.
Prove a row was in the snapshot TWMD published — and check it yourself.
Returns the Merkle sibling path, the signed root, and the checkpoint it belongs to. It returns
the PATH rather than a yes/no on purpose: a service that answers "yes, it is included, trust me"
is the opposite of verifiable. Recompute the root from the leaf and the path; the verifier is
~30 lines and is written out in docs/VERIFIABLE_DATA.md.
★ WHAT IT PROVES: integrity (the row was not altered after publication) and origin (the root was
signed by TWMD). **It does NOT prove the numbers are correct** — if the exchange published a
wrong figure, TWMD faithfully committed to the wrong figure. Do not present a passing proof as
a correctness guarantee.
★ THREE STATUSES, and they must not be collapsed:
ok proof enclosed; verify it.
not_in_snapshot that row_key was NOT a leaf of the snapshot. A true answer, NOT a failure and
NOT evidence of tampering.
no_checkpoint no snapshot was ever built for that dataset/version.
🔴 VERIFY ON THE CLIENT SIDE. There is deliberately NO `verify` tool here, and that is the
point: this server is the ISSUER of the proof, so a "we checked our own proof and it is fine"
tool would carry almost no evidential weight while looking exactly like independent
verification. Run the check where you are:
* Python SDK `twmd.Client.flight(..., proof_api=...).verify(dataset, row_key)`
-> {"verified": bool, "verification": {status, proves, checkpoint,
unverified_reason}} with FOUR values
(verified / failed / unproven / absent) — `unproven` is NOT `failed`.
* any language `scripts/twmd_verify_proof.py` — copy it; stdlib only, imports nothing from
our package (a verifier that needs our package asks you to trust our package).
* the core `twmd.proof_core` is the SAME SOURCE TEXT as that script, guarded per symbol
by tests/test_proof_core_is_one_source.py, so the two cannot drift.
Args:
dataset: e.g. 'daily_price'. row_key: the dataset's LOGICAL key joined by '|',
e.g. '2330|2026-08-14'. snapshot_version: omit for the most recent checkpoint.
read-only
Argument
Type
get_research
Retrieve one of YOUR previous research reports. Others' runs are simply not found.
Args: research_id — the identifier `run_research` returned when it started that run.
Show the price-trigger rules you have armed, and whether each is still armed.
A read-only inventory of thresholds you asked to be watched — it arms nothing and cancels
nothing (`set_price_alert` arms, `delete_alert` cancels). Another customer's triggers are
simply not visible here.
Browse an INDEX of your past backtest runs — ids and headline metrics only, no re-execution.
Use when you want to find a run whose id you have forgotten. It never re-computes anything:
`run_backtest` executes a new one, `get_backtest` opens a single record in full, and
`replay_backtest` re-derives one to check reproducibility. This is the catalogue, not any of
those three. Args: optional strategy_id filter, limit.
List available Taiwan-market datasets (discovery entry point). Returns id / 中文名 /
category / tier / one-line description for each. Use this first to find the right data.
Args:
category: optional, e.g. 'chip'(籌碼) 'fundamental'(基本面) 'price'(行情) 'macro'(總經)
'relation'(關聯/產業鏈) 'derivatives'(期權) 'event'(事件) 'rag_text'(文本).
tier: optional minimum plan: 'free' 'starter' 'pro' 'max' 'developer' 'enterprise'.
Verdicts from the overnight factor search on YOUR namespace — including the rejections.
The rejections are returned deliberately. A research log that keeps only the winners is the
highlight reel overfitting lives in, and the acceptance RATE is the number that tells you
whether the anti-overfitting gate is doing its job: a search that accepts most of what it
tries has a broken gate, not a talent for finding alpha.
Every verdict carries the trial count it was judged against, so it can be re-checked.
`coverage.missing` names hypotheses that were proposed but never judged because a cost
ceiling was reached — those are UNTESTED, not rejected.
An accepted factor is a FINDING with a run_id, not an allocation. Nothing here trades.
Args:
limit: how many verdicts to return, newest first (max 100).
Financial actions proposed by your research runs that are waiting for a human decision.
Nothing here has been executed or ever will be by this system. These are proposals.
Read the tickers on one named watchlist, as it stands right now.
A curated roster you maintain — distinct from `memory_search`, which digs through everything
you ever recorded, and from `list_alerts`, which is about price triggers rather than symbols
you are following. Args: key — the roster's name, default 'default'.
Re-run a remembered query by its `twmd_q_…` id, through the read API's own replay store.
This is what makes a recalled finding checkable: the memory says where it came from, and
this fetches that same data again. It never re-executes the query by another route — two
implementations of "replay" would be two answers to a question whose whole value is having
one.
`status` is one of:
found the bytes are here, with `result_hash` to check them against
too_large it WAS served, but exceeded the size ceiling: `result` is absent and
`result_hash` is authoritative — you can still verify a copy you hold
not_found never recorded (or pruned) — the citation cannot be resolved
Args: query_id — the `twmd_q_…` reference carried on a remembered finding's citation.
Remember something, with its sources and its knowledge time.
Nothing is ever overwritten: saving a `factor_def` or `watchlist` under an existing key
SUPERSEDES the previous version (both rows survive, so "what did I believe in June?" stays
answerable), and saving identical content twice is a no-op rather than a duplicate.
Args:
kind: 'query' | 'factor_def' | 'watchlist' | 'finding' | 'note'.
content: the thing to remember, as an object.
key: the stable name — REQUIRED for 'factor_def' and 'watchlist' (that is what makes a
definition reusable next session instead of re-invented).
as_of: the knowledge time this memory is about. Recall can bound on it, which is what
keeps a memory from leaking the future into a point-in-time question.
source_query_ids: the `twmd_q_…` ids behind this. REQUIRED for 'finding' — a conclusion
that cannot point at its data is not evidence, and will be refused.
agent_id: optional label for which of your agents wrote this.
Argument
Type
Description
key
any
kind*
string
as_of
any
content*
object
agent_id
any
source_query_ids
any
Input schema (JSON)
memory_search
Recall your own memories — hybrid (semantic + exact-term), with provenance attached.
Every result carries where it came from (`source_query_ids`, replayable), when it was
believed (`valid_from`/`valid_to`) and what knowledge time it is about (`as_of`), plus a
`recall` block stating which model and which filters produced the answer.
Args:
query: what you are looking for, in words.
kinds: restrict to some of 'query' 'factor_def' 'watchlist' 'finding' 'note'.
as_of: knowledge-time bound — pass a backtest's as_of and nothing recorded later can
come back. Use this for anything point-in-time.
key: the stable name, when you know it (e.g. a factor name).
agent_id: narrow to one of your agents.
include_superseded: also return old versions (the audit view). Default is current only.
Query rows with built-in look-ahead protection.
★ POINT-IN-TIME: pass `as_of` (YYYY-MM-DD) for backtesting/agent-learning. For non-point-in-time-safe
datasets (fundamentals, monthly_revenue, dividend_policy…) rows are filtered by DISCLOSURE date <= as_of,
so the agent only sees what was public at that moment. Omit as_of only for present-day lookups (warned).
★ IF A VALUE IS IN `coverage.missing`, IT IS NOT AVAILABLE. Say it is not available. **Never
estimate it, interpolate it, infer it from a neighbouring period, or carry the last known value
forward.** `coverage.missing` lists exactly what was requested and not returned, with a reason
(e.g. "9999 在 as_of 當日未上市"). An empty `data` array alongside a populated `missing` list is a
complete and correct answer to "what do you have" — not an invitation to fill the gap.
★ EVERY VALUE IS ATTRIBUTABLE. `provenance` carries {source, source_role, ingested_at, revision,
provenance_uri}; `meta.query_id` names this exact question. Quote the query_id when reporting a
number — `replay_query(query_id)` returns the bytes that were served, so the claim can be checked
later. `freshness.is_stale` is computed server-side against the dataset's own cadence budget;
`null` means it could not be determined, which is NOT the same as fresh.
Args:
dataset_id: see list_datasets. tickers: e.g. ['2330','2317']. start/end: 'YYYY-MM-DD' range.
as_of: knowledge-time cutoff 'YYYY-MM-DD' (use for backtests). limit: <=5000.
Returns: {meta:{table,coverage,row_count,as_of_applied,point_in_time_safe,warnings,query_id},
data:[...], provenance:{...}, coverage:{requested,returned,missing,reason},
freshness:{status,latest_available,expected_lag,is_stale}}
Example: query_dataset('income_statement', tickers=['2330'], as_of='2023-06-30')
read-only
Argument
Type
Description
end
any
as_of
any
query_regime
The Taiwan business-cycle light (NDC monitoring indicator) as a monthly series.
★ THIS IS A REVISED FIGURE, NOT A POINT-IN-TIME ONE. Every response carries
`revision_basis: as_revised`. Our source holds exactly one row per month — the CURRENT value,
not the value as first published — and it records no publication date.
★ `as_of` IS REFUSED, AND THE REFUSAL IS THE POINT. There is no honest point-in-time answer
here yet. Do NOT work around it by asking for a date range that ends at your as_of: the NDC
publishes a month's light about 27 days AFTER that month ends, so a range ending 2026-03-05
still contains the February light that was not public until late March. That is look-ahead, it
raises no error, and it makes a backtest look better than it was.
★ USE IT FOR CONTEXT, NOT AS A BACKTEST INPUT. Describing what regime the market is in today,
or labelling historical periods for narrative, is fine. Feeding it into a simulated decision
that claims to have been made at the time is not.
Args: optional start/end (YYYY-MM-DD), limit. `as_of` returns a refusal explaining the above.
Read the FULL TEXT of filings and announcements — with proof links and a knowledge cutoff.
★ NOT `search_filings`. That one ranks passages by similarity and hands you fragments; this hands
you whole documents so you can read what was actually said and where it sat in the filing.
Similarity is not importance, and a fragment cannot show you its own context.
★ POINT-IN-TIME: pass `as_of` (YYYY-MM-DD). The cutoff is applied in SQL on the source's declared
knowledge-time column BEFORE the row limit, so a bounded read is a true prefix of what was
knowable, not a random subset of it. **Without `as_of` the read is NOT point-in-time** and says
so in `warnings`.
★ NO SENTIMENT, NO SCORES — deliberately. Judging the text is your job. A stored score is one
model's output on one day; after that model changes, the stale number still sits in the table
looking exactly like a fresh one.
★ READ `corpus_reality` BEFORE CONCLUDING ANYTHING. The full-text corpus is SMALL and the
response says how small. One source carries ~1M rows of TITLES ONLY — a large row count there is
breadth, not depth, and "what did they say about it" is not answerable from titles.
★ A ticker that returns nothing appears in `coverage.missing`. That means nothing is held for it
under those filters — NOT that the company disclosed nothing. Do not fill the gap.
Args:
source: which corpus, e.g. 'announcements_fulltext' or 'mops_major_event'.
tickers: restrict to these codes, e.g. ['2330'].
as_of: knowledge cutoff (YYYY-MM-DD).
since: optional lower bound on the same knowledge-time column.
limit: max documents (these are whole documents; keep it small).
read-only
Argument
Type
Description
as_of
any
limit
integer
since
replay_backtest
Re-run a stored backtest and report whether it still produces the same numbers.
Same spec, same `as_of`, same data questions. If the numbers moved, either the engine
version changed or the underlying data was restated — both are reported, neither is
smoothed over. Args: run_id.
Measure a portfolio you state against limits you state, on official point-in-time prices.
Reports concentration and peak-to-trough drawdown, and NAMES every position it could not
price rather than quietly assessing the rest — an assessment covering 60% of a portfolio
without saying so is worse than none.
Any breach produces a PROPOSAL (e.g. "reduce 2330") that requires a human decision.
Approving a proposal records that decision; it executes nothing. TWMD has no order path.
Args:
positions: `[{"ticker": "2330", "quantity": 100}, ...]`. YOUR stated holdings — nothing
is read from a brokerage account, because no such connection exists.
as_of: knowledge cutoff, `YYYY-MM-DD`. Defaults to the latest available data.
max_position_weight: single-name limit as a fraction (0.35 = 35%).
max_drawdown: peak-to-trough limit as a fraction (0.25 = 25%).
Submit a point-in-time backtest. NON-BLOCKING: returns a run_id at once; poll get_backtest.
`job_status` tells you where it is: `cached` (this exact backtest was already computed — the
stored result is returned immediately), `done` (it finished within `wait_seconds`), or
`queued` / `running` (call `get_backtest(run_id)` until it is `done` or `failed`). The same
spec always has the same run_id, so re-submitting is free and never computes twice.
The run may only see data stamped on or before `as_of` — that is enforced structurally, not
by convention. Results arrive with the data `query_ids` behind them and an anti-overfitting
verdict (out-of-sample, deflated Sharpe, multiple-comparison, crash stress); a run that
fails the gate is returned REJECTED with reasons rather than hidden.
Args:
strategy_id: a registered strategy, e.g. 'buy_and_hold' or 'cross_sectional_momentum'.
start / end: the測試期間 (YYYY-MM-DD). `end` must not be after `as_of`.
as_of: the knowledge cutoff. REQUIRED — there is no "today" default.
tickers: required when universe_kind='explicit'; ignored for 'point_in_time'.
universe_kind: 'point_in_time' (survivorship-safe, resolved from listing/delisting
dates at each rebalance) or 'explicit' (a list you supplied).
market: optional market filter for a point-in-time universe.
rebalance: 'daily' | 'weekly' | 'monthly' | 'quarterly'.
cost_bps: one-way transaction cost in basis points.
params: strategy parameters, e.g. {'lookback_days': 60, 'top_k': 5}.
wait_seconds: how long this call may wait for a FAST run to finish before answering
`queued`/`running` (0 = answer at once; capped at 10). It never waits for a slow run.
costs: the trading costs to simulate, e.g. {"model": "tw_default_model"} (standard:
0.1425% commission both sides + 0.3% securities tax on sells + slippage),
{"model": "tw_default_model", "fee_discount": 0.28} (a 2.8-fold broker discount on
the commission; the tax is statutory and cannot be discounted), or {"model": "none"}
(commission and tax zeroed — the result is flagged as not a real-world result).
Omit for standard. Do not combine with cost_bps. The response's `costs` says exactly
what was applied.
Strategies served by the Zipline engine are limited to a 3-year window per interactive
request (`error: "window_exceeds_interactive_cap"`, with a suggested window). Full-period
results for the strategy-library strategies are precomputed nightly on the marketplace.
This measures history. It is not advice and it places no orders.
run_recipe
Replay a saved multi-step routine over rows you fetched, with every step listed.
★ THREE ROUTINES: `momentum_scan` (rank by a return column), `earnings_surprise`
(actual versus estimate), `dividend_capture` (which ex-dates are still ahead).
★ A SAVED ROUTINE IS NOT A TRADING VIEW. The names are conventional labels for well-known
sequences; what runs is arithmetic over rows you supplied. `steps[]` spells out each operation
so you can disagree with the routine rather than trust it, and nothing here says any of these
sequences makes money.
★ SKIPPED SYMBOLS ARE LISTED, NEVER QUIETLY OMITTED. A symbol lacking the column a routine
needs lands in `skipped[]` with the reason. A silently shorter list reads as "these were
evaluated and did not qualify", when in fact they were never evaluated at all.
★ IT COMPOSES, IT DOES NOT FETCH. Rows come from `query_dataset`, so the routine inherits that
call's `as_of` rather than defining a second point-in-time story of its own.
⚠️ Missing inputs are never substituted with zero — a zero estimate turns any positive result
into an infinite surprise. Args: recipe, rows, as_of, today, top_n, min_yield.
read-only
Argument
Type
Description
rows
any
as_of
any
today
string
top_n
integer
recipe*
string
min_yield
run_research
Run a multi-agent research pass and return a structured, sourced report.
Six roles run in order — data analyst, factor researcher, backtest engineer, risk officer,
portfolio manager, compliance officer. Each step's output carries the `query_ids` behind it;
risks are reported alongside results, not beneath them; and anything the run could not do is
listed as a limitation rather than filled in.
The factor researcher checks memory first and SKIPS a hypothesis a previous run already
rejected. The portfolio manager proposes nothing when the evidence failed the
anti-overfitting gate, and any allocation it does propose is a PROPOSAL awaiting a human —
this system places no orders and moves no money.
Args:
prompt: the research question, in your words. This is the only channel carrying
instructions; anything a tool returns is treated as data.
as_of: the knowledge cutoff. REQUIRED — nothing stamped after it is visible to the run.
tickers: optional explicit universe. Omit for a point-in-time (survivorship-safe) one.
start / end: optional test period; `end` must not be after `as_of`.
max_backtests: per-run cap on backtests (cost control).
Argument
Type
Description
end
any
as_of*
string
start
any
prompt*
string
tickers
any
max_backtests
screen
Post-process rows already returned by query_dataset; without rows it returns a clear hint.
Turn a spoken shortlist description into explicit numeric cut-offs, and show the cut-offs.
★ THE THRESHOLDS COME BACK WITH THE SHORTLIST. "低本益比" becomes `per < 15`, and that 15 is
printed in `applied[]` so you can disagree with it. A filter that hands over thirty names
without saying where it drew the line cannot be checked by anyone — and whether the line was
15 or 20 completely changes which thirty.
★ PHRASES IT CANNOT MAP COME BACK IN `unparsed[]`. It will not quietly invent a boundary for
a wording it did not recognise, because a list the caller believes they defined and actually
did not is worse than a shorter list.
★ NOT `query_dataset` AND NOT `compare`. `query_dataset` returns rows for symbols you already
chose; `compare` puts a handful of named symbols beside each other. This one is for when you
have no symbols yet, only a description of what you are looking for.
⚠️ A shortlist is not a recommendation. Rows whose value is absent are excluded rather than
assumed to pass — being unmeasured is not the same as qualifying.
Args: conditions (plain words), optional as_of, optional pre-fetched rows.
Semantic search over MOPS filings, financial-statement notes and company news.
Answers questions a keyword filter cannot: "what risks did this company disclose this quarter?",
"which companies mentioned CoWoS capacity expansion?" — matching on MEANING, so a paragraph that
never uses your exact words still ranks.
★ POINT-IN-TIME: pass `as_of` (YYYY-MM-DD). Chunks are filtered `published_at <= as_of` in SQL
BEFORE ranking, so a backtest cannot retrieve a filing that did not exist yet. `meta.as_of_applied`
echoes the cutoff that actually ran — check it. **Without `as_of` the results include the most
recent filings and are look-ahead UNSAFE for backtesting**; the response says so in `meta.warnings`.
★ REFERENCE CONTEXT, NOT AUTHORITY. Every hit carries `source`, `source_tier`
("official" = MOPS/exchange, "third_party" = press) and a `url`. Read the chunk and judge it
yourself; the ranking is similarity, not importance, and similarity is not evidence.
**Nothing here is investment advice** (`not_investment_advice: true`).
★ A ticker you asked about that returns nothing appears in `coverage.missing` with a reason.
That means NOTHING IS INDEXED for it under those filters — it does NOT mean the company
disclosed nothing. Do not fill the gap.
Args:
query: what to look for, in Chinese or English (e.g. '匯率風險', 'CoWoS capacity').
tickers: restrict to these codes, e.g. ['2330','2317'].
doc_type: e.g. 'mops_major_event', 'financial_note', 'company_news'.
as_of: knowledge-time cutoff 'YYYY-MM-DD' — use it for anything backtest-shaped.
source_tier: 'official' to exclude third-party press.
limit: <= 100.
Returns: {data:[{ticker,doc_type,published_at,source,source_tier,url,chunk_text,similarity}],
meta:{as_of_applied,point_in_time_safe,embedding_model,warnings,query_id},
provenance:{...}, coverage:{requested,returned,missing,reason}, freshness:{...}}
read-only
Argument
Type
Description
as_of
any
set_price_alert
Leave a standing instruction: tell me when this symbol crosses this price.
The alert OUTLIVES this conversation. It is evaluated against official daily closes by a
resident agent and delivered to your realtime stream and to any webhook endpoints you have
registered — signed, retried, and de-duplicated so one crossing is one notification.
This is a NOTIFICATION, not an order. Nothing in TWMD can place a trade.
Args:
rule_id: your name for this alert. Re-using one UPDATES it rather than adding a second.
symbol: the Taiwan ticker, e.g. "2330".
direction: "below" or "above".
threshold: the price level, in TWD.
label: optional human-readable note carried on the alert.
edge_triggered: True (default) fires on the CROSSING only — one long slump does not
notify you every day. False fires on every bar that is past the level.
Argument
Type
Description
label
any
symbol*
string
rule_id*
string
direction*
string
threshold*
number
edge_triggered
boolean
Input schema (JSON)
supply_chain_read
供應鏈與同業:產業鏈位置、同業分組、轉投資、外銷訂單 vs 營收。
★ 事實整理,不是投資建議、不是預測。輸出帶 `does_not_prove`。
★ ⚠️ `company_peer_groups` 無日期欄:指定過去的 `as_of` 時,回的是**現況**分組,
`pit_notes` 會標「非當時分類」。
★ ⚠️ 質化的客戶/供應商關係與分部營收在年報附註,那條來源**尚未建置** ——
固定列在 `cannot_see`(status=no_loader),不是這次查失敗。
★ Args:`ticker`(例如 "2330")、`as_of`(選填)。
★ Returns:誠實信封;外銷訂單與營收的背離會 listed in `concerns`。
★ PRECONDITIONS:自己取數;requires 一個已識別的 session。
★ SIDE EFFECTS:read-only,does not 寫入。
Hand an unregistered caller a short taste of an open dataset, plus where to unlock the rest.
★ WHAT AN ACCOUNTLESS CALLER GETS INSTEAD OF A BARE REFUSAL. Somebody arriving through a chat
connector with no plan would otherwise meet a flat rejection, which the host model relays as
"this service turned you down".
★ WHAT IS ACTUALLY FREE, STATED CONCRETELY. The reference resources read with no key at all,
and the five sample tickers (2330, 2317, 2454, 0050, 2603) answer through `query_dataset` with
no plan. Querying beyond those over MCP starts at the Pro plan.
★ OPEN DATASETS YIELD A FEW MARKED ROWS; PAID ONES YIELD NONE. For a paid dataset the row
list is not even consulted, so no figure can escape through this path regardless of what the
caller passes in. `rows_withheld` states how many were held back, because a taste that does not
say it is a taste reads as the whole thing.
★ NOT `query_dataset`. That one serves entitled callers in full. This exists only for the
moment somebody hits the edge of what they are entitled to.
⚠️ Withdrawn datasets stay refused here too — a licence ruling is not a tier, so no amount of
signing up unlocks them. Args: dataset, keyless_eligible, rows.