Prove every historical value. Don't trust it.
The failure mode quantitative research fears most is data that changed quietly. TWMD publishes a cryptographically signed, point-in-time checkpoint for every snapshot, so you can prove independently that a historical extract is the one we served at the time and has not been altered since — without trusting us. Built for backtest integrity.
{
"dataset": "twse_daily_price",
"snapshot_version": "2026-08-14",
"root": "1b5b0b1995eb389f3ea7807f51d22ffcee8c4dedbc1c87d5d11d36ad033b95b9",
"leaf_count": 1378,
"path_length": 11,
"merkle_path_reaches_root": true,
"verified": true,
"proves": "integrity and origin, NOT semantic correctness"
}How it works
Three steps, all of them public.
There is no stage in this that happens inside a system only we can see. That is the design constraint, not a side effect of it.
- 01
Signed checkpoints
Each snapshot of a dataset is reduced to a Merkle root. That root is signed with our Ed25519 private key and published. The signature commits us to one specific version of history at one specific time.
- 02
A published algorithm
The construction is served as data at /v2/proof/recipe — hash function, domain separation, leaf encoding, path direction. Anyone can recompute a leaf from it, or write a second verifier without reading a line of our code.
- 03
You run the check
Download twmd_verify_proof.py, run it in your own environment. It recomputes the leaf, walks the Merkle path to the root, and checks the Ed25519 signature over that root. Standard library only — nothing from us and nothing from PyPI.
Proof endpoints
Four endpoints, no key required.
Open each one in a browser. A proof you needed our credentials to check would not be a proof of anything.
What is covered today
Read from the checkpoint endpoint as this page was served. Coverage is being extended; a short true list is worth more here than a long intended one.
- tdcc_shareholding_distributionsnapshot 2026-08-07-report_date · 40,188 rows · signed by ed25519:9b3481cf1cf7780a
- major_event_taxonomysnapshot 2026-09-04-event_date · 173 rows · signed by ed25519:9b3481cf1cf7780a
- valuation_datasnapshot 2026-09-02-as_of_date · 1,081 rows · signed by ed25519:9b3481cf1cf7780a
- valuation_core_dailysnapshot 2026-09-03-date · 2,346 rows · signed by ed25519:9b3481cf1cf7780a
- twse_daily_pricesnapshot 2026-09-03-trade_date · 1,380 rows · signed by ed25519:9b3481cf1cf7780a
- trading_calendarsnapshot 2026-09-03-trade_date · 2 rows · signed by ed25519:9b3481cf1cf7780a
- tpex_daily_pricesnapshot 2026-09-03-trade_date · 1,013 rows · signed by ed25519:9b3481cf1cf7780a
- taifex_put_call_ratiosnapshot 2026-09-02-trade_date · 1 rows · signed by ed25519:9b3481cf1cf7780a
- taifex_options_settlement_pricesnapshot 2026-09-02-trade_date · 12,388 rows · signed by ed25519:9b3481cf1cf7780a
- taifex_options_deltasnapshot 2026-09-02-trade_date · 8,520 rows · signed by ed25519:9b3481cf1cf7780a
- taifex_inst_oisnapshot 2026-09-03-trade_date · 69 rows · signed by ed25519:9b3481cf1cf7780a
- taifex_atm_ivsnapshot 2026-09-03-trade_date · 1 rows · signed by ed25519:9b3481cf1cf7780a
- stock_price_limit_dailysnapshot 2026-09-03-trade_date · 894 rows · signed by ed25519:9b3481cf1cf7780a
- securities_lending_availablesnapshot 2026-09-03-trade_date · 2,097 rows · signed by ed25519:9b3481cf1cf7780a
- return_index_dailysnapshot 2026-09-03-trade_date · 1 rows · signed by ed25519:9b3481cf1cf7780a
- price_move_contextsnapshot 2026-09-03-trade_date · 434 rows · signed by ed25519:9b3481cf1cf7780a
- options_daily_taifexsnapshot 2026-09-03-trade_date · 11,010 rows · signed by ed25519:9b3481cf1cf7780a
- market_overview_snapshotssnapshot 2026-09-03-as_of_date · 1 rows · signed by ed25519:9b3481cf1cf7780a
- market_indexsnapshot 2026-09-03-as_of_date · 1 rows · signed by ed25519:9b3481cf1cf7780a
- market_breadthsnapshot 2026-09-03-as_of_date · 2 rows · signed by ed25519:9b3481cf1cf7780a
- major_event_taxonomysnapshot 2026-09-03-event_date · 182 rows · signed by ed25519:9b3481cf1cf7780a
- limit_eventssnapshot 2026-09-03-trade_date · 42 rows · signed by ed25519:9b3481cf1cf7780a
- institutional_flowsnapshot 2026-09-02-trade_date · 1,338 rows · signed by ed25519:9b3481cf1cf7780a
- institutional_by_typesnapshot 2026-09-02-trade_date · 17,788 rows · signed by ed25519:9b3481cf1cf7780a
- industry_index_dailysnapshot 2026-09-03-date · 37 rows · signed by ed25519:9b3481cf1cf7780a
- governance_t187ap33_lsnapshot 2026-09-02-as_of_date · 1,094 rows · signed by ed25519:9b3481cf1cf7780a
- futures_daily_contextsnapshot 2026-09-02-trade_date · 1 rows · signed by ed25519:9b3481cf1cf7780a
- foreign_holdingsnapshot 2026-09-02-trade_date · 1,362 rows · signed by ed25519:9b3481cf1cf7780a
- derivatives_marketsnapshot 2026-09-03-trade_date · 2,385 rows · signed by ed25519:9b3481cf1cf7780a
- day_trading_suspensionsnapshot 2026-09-03-suspension_start_date · 8 rows · signed by ed25519:9b3481cf1cf7780a
- day_tradingsnapshot 2026-09-02-trade_date · 1,232 rows · signed by ed25519:9b3481cf1cf7780a
- block_trade_dailysnapshot 2026-09-03-trade_date · 4 rows · signed by ed25519:9b3481cf1cf7780a
- attention_disposal_eventssnapshot 2026-09-03-event_date · 38 rows · signed by ed25519:9b3481cf1cf7780a
- valuation_core_dailysnapshot 2026-09-02-date · 2,351 rows · signed by ed25519:9b3481cf1cf7780a
- twse_daily_pricesnapshot 2026-09-02-trade_date · 1,380 rows · signed by ed25519:9b3481cf1cf7780a
- trading_calendarsnapshot 2026-09-02-trade_date · 2 rows · signed by ed25519:9b3481cf1cf7780a
- tpex_daily_pricesnapshot 2026-09-02-trade_date · 1,013 rows · signed by ed25519:9b3481cf1cf7780a
- taifex_put_call_ratiosnapshot 2026-09-01-trade_date · 1 rows · signed by ed25519:9b3481cf1cf7780a
- taifex_options_settlement_pricesnapshot 2026-09-01-trade_date · 12,030 rows · signed by ed25519:9b3481cf1cf7780a
- taifex_options_deltasnapshot 2026-09-01-trade_date · 8,794 rows · signed by ed25519:9b3481cf1cf7780a
- taifex_inst_oisnapshot 2026-09-02-trade_date · 69 rows · signed by ed25519:9b3481cf1cf7780a
- taifex_final_settlementsnapshot 2026-09-01-settlement_date · 1 rows · signed by ed25519:9b3481cf1cf7780a
- taifex_atm_ivsnapshot 2026-09-02-trade_date · 1 rows · signed by ed25519:9b3481cf1cf7780a
- stock_price_limit_dailysnapshot 2026-09-02-trade_date · 895 rows · signed by ed25519:9b3481cf1cf7780a
- short_sale_balance_controlsnapshot 2026-09-01-trade_date · 1,301 rows · signed by ed25519:9b3481cf1cf7780a
- return_index_dailysnapshot 2026-09-02-trade_date · 1 rows · signed by ed25519:9b3481cf1cf7780a
- price_move_contextsnapshot 2026-09-02-trade_date · 373 rows · signed by ed25519:9b3481cf1cf7780a
- options_daily_taifexsnapshot 2026-09-02-trade_date · 11,712 rows · signed by ed25519:9b3481cf1cf7780a
- market_value_weightsnapshot 2026-08-25-as_of_date · 1,079 rows · signed by ed25519:9b3481cf1cf7780a
- market_overview_snapshotssnapshot 2026-09-02-as_of_date · 1 rows · signed by ed25519:9b3481cf1cf7780a
Run it yourself
The verifier is a script, not a page.
Standard library only: it imports nothing from us and nothing from PyPI. A verification performed inside our JavaScript would be asking you to trust our JavaScript, which is the thing verification is for.
curl -O https://twmarketdata.com/twmd_verify_proof.py
python3 twmd_verify_proof.py --api https://api.twmarketdata.com --dataset tdcc_shareholding_distribution --row-key "00400A|TWSE|2026-08-07|TDCC_01|tdcc_official|shareholding_distribution"Download twmd_verify_proof.py →
Reading the output
- root
- the signed Merkle root for that snapshot — the thing we committed to
- leaf_count
- how many rows the root covers
- path_length
- how many hashes were recomputed to walk from your row to the root
- verified
- the path reached the root and the Ed25519 signature over it checked out
proves: "integrity and origin, NOT semantic correctness"
It proves the record was not altered and that it came from us. It does not prove the number is right. If an official source published a wrong figure, this attests faithfully to the wrong figure — and a system that claimed otherwise would be claiming to prove the exchange correct, which nothing can.
Point-in-time
Know when it could have been known.
Disclosure data carries a knowledge date on every row, set conservatively to the statutory filing deadline — so a backtest filtered on it cannot see a figure before the market could.
A knowledge date on every disclosure row
Monthly revenue, income statement, balance sheet and cash flow all carry knowledge_date, alongside kd_source saying where it came from and kd_imputed saying whether it was observed or derived. Align on that column rather than on the period end date and a figure enters your backtest when it entered the market, not when the quarter closed.
It is the statutory deadline, which is late by design
Measured 2026-08-19: for every disclosure dataset checked, kd_source is statutory_deadline and kd_imputed is true — the date is the filing deadline in law, not the observed announcement, which we do not yet capture. Companies file on or before the deadline, so this is late rather than early: a fast filer looks slower than it was. That costs realism and it cannot leak, which is the safe direction to be wrong in.
Daily prices do not carry one
twse_daily_price has no knowledge_date column. For an end-of-day price the trade date is the publication date, so the gap is narrow — but it is a real gap, and an agent that assumes the column is universal will get an undefined rather than a date. Per-dataset point-in-time status is listed on the coverage page.
An as-of cutoff on REST
Measured 2026-08-22: applied on the REST dataset endpoints. Passing as_of moves the newest row back to the cutoff: 2330 daily prices return 2026-08-21 without it and 2020-06-30 with as_of=2020-06-30. The response reports as_of_applied, the field it filtered on, and how many rows the cutoff removed, so you can confirm it took effect rather than take our word for it. It is a KNOWLEDGE-time cutoff, not a date truncation — at as_of=2021-03-31 monthly revenue returns February, because February was not filed until March. On disclosure datasets that knowledge date is the statutory filing deadline rather than the observed announcement, which makes data available no earlier than it truly was: conservative, and safe for a backtest, but not exact. Daily prices carry no filing deadline and are cut on the trade date itself.
Why it matters
An assurance you can execute.
The difference is not that others are careless. It is that a sentence in a contract cannot be tested and a signature can.
An assurance you can execute
The industry norm is a sentence in a contract: history will not be revised without notice. That is a promise, and a promise is only as good as the party giving it. A signature over a Merkle root is the same assurance in a form you can test, in your own terminal, years later.
What it is actually used for
Backtest integrity, so a result can be re-derived from provably identical inputs. Audit, so a decision made on a figure can be tied to the figure as it stood. Compliance record-keeping, where the ability to demonstrate that an input was not retro-fitted is the whole point.
The claim we are not making
This is not a tamper-proof live database, and we do not claim one. What is provable is narrower and stated exactly: for a checkpoint we have published, that the row you hold is the row that root committed to. Anything published after an alteration would still be signed — the point is that the earlier root cannot be un-signed.
Tool surface
What the agent may call, committed to the same log as the data.
A tool description is an instruction to a model, not documentation about it. Change one quietly and you have changed what the agent does, without touching a single row of data — which is why the tool surface is hashed, signed and appended to the same transparency log the datasets use, rather than left as a page we could edit.
What is anchored, and what verifies
24 tools, hashed into one surface hash and appended to the meta-log at index 157 on 2026-08-21. Two things were checked rather than taken on trust: the log entry's root is byte-identical to the hash the manifest reports, and the hash recomputes from the served tool list using the recipe the endpoint publishes. So the surface that was attested is the surface the log committed to, and the list you are shown is the list that was hashed — and because the log is append-only, that entry cannot later be removed or reordered without the consistency proof failing. The ed25519 signature over the published payload verifies against the same key that verifies the log's own entries, so the chain runs end to end without trusting us at any step.
Why the field names look odd
The entry stores a flat sha256 in a field called merkle_root, and the response says so rather than letting anyone waste an afternoon building a tree that computes something else. The name is frozen because the append-only log hashes the field set — renaming it would rewrite history to fix a label, which is the trade this whole page refuses.
Served at /v2/proof/tool-manifest, keyless. It reports the hash recipe and the exact signing convention, so a check can be written against the specification rather than against our implementation.
Consistency
The same transparency mathematics your browser already trusts.
Every certificate your browser accepts is checked against Certificate Transparency: an append-only Merkle tree, a signed head, and audit paths that let anyone test membership without trusting the log operator. The proofs on this page are built on that same construction — RFC 6962's leaf and node hashing, its domain-separation prefixes, and its rule against duplicating an odd node. What is shared is the mathematics. What is not shared is the governance: CT is watched by independent auditors gossiping about what they saw, and we have no such witnesses.
What an inclusion proof settles today
That the row you hold is the row a published root committed to. You recompute the path, check the signature against a key we do not hold the only copy of, and get a yes or a no. That part is live and needs no key.
What it cannot settle
How that tree relates to any earlier one. A signed root proves what it committed to; it says nothing about whether an earlier root committed to something different. The signed envelope says so itself, in a does_not_prove field returned with every checkpoint — we would rather the API state its own limits than have this page be the only place they appear.
What a consistency proof would add
A test that an old tree is a prefix of a new one — that history grew rather than being rewritten. Without it, a reader has our word that earlier roots still stand for what they stood for; with it, that becomes another thing checked in your own terminal rather than believed.
Served at /v2/proof/consistency. The verifier checks it in the same run as the inclusion proof, and the transcript below is real output from a real invocation.
step 1 GET /v2/proof/meta-log entries [0, 157)
157 entries fetched
step 2 recompute each leaf from its own sth_canonical
157 leaves recomputed, 0 disagreed with the served leaf_hash
step 3 recompute both roots from those leaves
first ( 40) 06d57911469a82646ef1606124fab4060d4c5ea6dc3f915db10d48756fcb744c
second (157) 117c1c779f67cc8a04e6c4a0d0117814ba3053448e670dd96b215b9672753876
step 4 compare against the roots the API serves
first MATCH served 06d57911469a82646ef1606124fab4060d4c5ea6dc3f915db10d48756fcb744c
second MATCH served 117c1c779f67cc8a04e6c4a0d0117814ba3053448e670dd96b215b9672753876
result PASSstep 1 GET /v2/proof/meta-log entries [0, 157)
157 entries fetched
step 2 recompute each leaf from its own sth_canonical
157 leaves recomputed, 1 disagreed with the served leaf_hash
step 3 recompute both roots from those leaves
first ( 40) dba35b59d92d9c7a2a31238c8eb8d6d01b6e0d3424374c7e3de338a61787a383
second (157) dd8e8daf78f12b140cdea70e210dac08973dc265fee0c02159b3b897318047b5
step 4 compare against the roots the API serves
first MISMATCH served 06d57911469a82646ef1606124fab4060d4c5ea6dc3f915db10d48756fcb744c
second MISMATCH served 117c1c779f67cc8a04e6c4a0d0117814ba3053448e670dd96b215b9672753876
result FAIL — a recomputed root does not match the published oneLeft: the check as it runs against the log today. Right: the same check with one entry altered before hashing — it fails at the leaf and then at both roots. A verification that only ever prints PASS cannot be told apart from one that always prints PASS, which is why the second run is shown.
These two roots stay valid as the log grows: a Merkle root over the first N entries does not change when entry N+1 is appended. So this run remains checkable rather than becoming a historical curiosity — and if either root ever stops matching, that is not a page gone stale, it is the log having been rewritten.
What this proof does not claim
- That the data trees those heads describe are themselves append-only. They are ordered by row key, which is a different property.
- That the underlying figures are correct.
- When any entry was appended — a signature cannot witness its own timing.
One visit is not enough
Compare the roots against ones you recorded on an earlier visit. A log can only be shown to be append-only relative to something you saw before — a single visit proves nothing, however many hashes it recomputes.
Superset
Is one snapshot a subset of another — and can the two even be compared?
Version 1 had one answer for two very different situations: a row we had genuinely lost, and a question that never made sense because the two snapshots covered different periods. Version 2 asks about comparability first, and only then about the leaves. The four runs below are here to show it gives different answers in different situations — one ok and three refusals, each for its own reason.
A week inside its month — ok
Live APIGET /v2/proof/superset/v2?dataset=twse_daily_price&from=2026-08-w3&to=2026-08
status ok
proof_version twmd-superset/v2
from 2026-08-w3 merkle_root 2bb461fdcfc4… leaf_count 6886
scope trade_date in [2026-08-17, 2026-08-22)
to 2026-08 merkle_root 5d1a0870103b… leaf_count 20663
scope trade_date in [2026-08-01, 2026-09-01)
scope.comparability.comparable true
scope.comparability.reason to_scope_contains_from_scope
claim every leaf of `from` is still a leaf of `to`
(and `to`'s scope contains `from`'s)
signed_by ed25519:9b3481cf1cf7780a
signature /f4iSVUCtgPcI3EEJaJScKfXbaOVcXgFX4jJV4jomYZ…
verify_recipe_url /v2/proof/recipe
public_key_url /v2/proof/public-key
entry_count 5000
entries_truncated true
next_cursor 4952|TWSE|2026-08-17|official_twseWhat this run establishes: Every row in the narrower window is still in the wider one, and the wider scope contains the narrower — so a row missing here would be missing from our data rather than outside the question. Note the pagination: inclusion entries come 5,000 to a page and this week has 6,886, so two pages. The status covers ALL of the leaves — that verdict is the server's, over the whole set. Rebuilding from.merkle_root yourself means following next_cursor through both pages first; this is not one call that proves it end to end.
Two windows that do not overlap — refused
Live APIGET /v2/proof/superset/v2?dataset=twse_daily_price&from=2026-08-w3&to=2026-08-w1
status scope_not_comparable
reason to_scope_narrower
from 2026-08-w3 trade_date in [2026-08-17, 2026-08-22)
to 2026-08-w1 trade_date in [2026-08-03, 2026-08-08)
message `to` covers [2026-08-03, 2026-08-08), which does not contain
`from`'s [2026-08-17, 2026-08-22)…
means this is a statement about the QUESTION, not about our data.
…the leaf sets were not compared at all.
(no removed_row_keys, no removed_count)What this run establishes: This is the case version 1 got wrong. Asking whether week three is inside week one is not a question about our data, and the absence of removed_row_keys is the proof that nothing was compared: the endpoint declined the comparison rather than reporting thousands of rows as 'missing'. A tool that answers a nonsensical question with an alarming number teaches people to ignore its alarms.
A snapshot from before scope existed — refused
Live APIGET /v2/proof/superset/v2?dataset=twse_daily_price&from=2026-08-14&to=2026-08
status scope_not_comparable
reason scope_unknown
from 2026-08-14 (id 1) merkle_root 1b5b0b1995eb… leaf_count 1378
kind: unknown — scope not recorded
to 2026-08 trade_date in [2026-08-01, 2026-09-01)
message `from`: this snapshot was published before scope was recorded
(superset/v1), so what it covers is not known. It is refused
rather than assumed: assuming a scope would manufacture the
comparability this endpoint exists to establish.What this run establishes: Every snapshot published before scope recording carries kind: unknown, and v2 refuses all of them rather than guessing. The refusal is the feature: inventing a scope would fabricate exactly the comparability the endpoint is supposed to prove. This run uses a checkpoint that genuinely exists in production — id 1 — rather than a synthetic one, because a designed-in refusal is worth showing on real data.
A row missing from the wider snapshot — not provable
Reproduced locallyGET /v2/proof/superset/v2?dataset=twse_daily_price&from=…&to=…
status not_provable
reason leaf_set_not_a_superset
removed_count 1
removed_row_keys ["2317|TWSE|2026-08-14|official_twse"]
message row(s) published in `from` are absent from `to`, or present with
different bytes. This is a TRUE answer about our data, not a
failed query.What this run establishes: What the endpoint does when a row really is gone: it names the row. This one is reproduced on a temporary local database, because production does not manufacture a missing row and we will not fake one to make a better screenshot. The label is the point — on a page whose entire argument is that claims should be checkable rather than impressive, an unlabelled local transcript would refute the page it appears on.
Reading the scopes
Scopes are half-open: [period_from, period_to), upper bound exclusive.
/v2/proof/superset/v2 needs no key. The response carries its own verify recipe and the public key URL, so the roots below can be recomputed without running anything of ours.
What this proof does not claim
This establishes comparability and subset completeness. It does not establish that the underlying figures are correct — the same boundary every other proof on this page keeps.
Frequently asked questions.
1What does it prove, and what does it not?
2How do I run it?
3Which datasets are covered?
4Is the data point-in-time correct?
5Do I have to trust TWMD?
Reviewing us as a vendor?
The Trust Center carries the subprocessor table, the measured grades, the certification roadmap and the self-assessment — including the parts we have not done yet.