What are the pitfalls in Taiwan institutional investor flow data?
Three. First, do not compute net as buy minus sell — those legs can be null while the net fields carry values; read foreign_net_buy_sell and its siblings instead. Second, the listed and OTC markets are separate datasets, so check the market field before you aggregate or you will count the same thing twice. Third, a day's figures are not necessarily final; updated_at tells you when the row was last written.
The three institutional investors
Foreign investors, investment trusts and dealers — the three groups Taiwan publishes net buying and selling for after each close, sourced from TWSE T86.
Pitfall one: the buy and sell legs can be null
The response carries buy, sell and net fields for each group, but the legs are not guaranteed to be populated — in the rows measured, foreign_buy and foreign_sell were null while foreign_net_buy_sell held a value. Computing buy minus sell therefore returns zero, or raises on None. Read the net fields.
import requests
r = requests.get(
"https://api.twmarketdata.com/v2/datasets/institutional-flow",
headers={"X-API-Key": "sk_live_your_key"},
params={"symbol": "1301", "start_date": "2026-08-01", "end_date": "2026-08-04"},
timeout=20,
)
r.raise_for_status()
for row in r.json()["rows"]:
print(row["date"], row["market"],
"foreign net", row["foreign_net_buy_sell"],
"| buy leg", row["foreign_buy"], "sell leg", row["foreign_sell"])
# The legs may be None. Check before subtracting.The three nets sum to the total — use it as a free check
Foreign, investment trust and dealer nets add up to total_institutional_net_buy_sell, exactly, on the rows measured. That gives you a self-test on your own pipeline: if your numbers stop reconciling, the problem is in your handling rather than in the data.
for row in r.json()["rows"]: parts = (row["foreign_net_buy_sell"], row["investment_trust_net_buy_sell"], row["dealer_net_buy_sell"]) if None not in parts and sum(parts) != row["total_institutional_net_buy_sell"]: print("does not reconcile:", row["date"], sum(parts), row["total_institutional_net_buy_sell"])Pitfall two: aggregating across markets double counts
Every row carries a market field, TWSE or TPEx. The two markets are published separately, so summing every row over a period counts the same concept twice and overstates whole-market flow. Filter by market for a single market; for the whole market, first confirm no symbol and date appears twice.
rows = r.json()["rows"]twse_only = [x for x in rows if x["market"] == "TWSE"] seen = set()for x in rows: key = (x["symbol"], x["date"], x["market"]) if key in seen: print("duplicate row:", key) seen.add(key)Pitfall three: today's number may still move
After-hours priced and block trades can be added after the first publication, so a day's totals can shift slightly. Each row carries updated_at, when the row was last written, and as_of_date, the day it describes. Use those to judge whether a figure is fresh enough to be provisional, rather than guessing.
for row in r.json()["rows"]: print(row["date"], "as of", row["as_of_date"], "last written", row["updated_at"]) # Written close to its own trading day: likely still subject to additions.Sub-categories are not in this response
If your analysis needs dealer proprietary versus hedging activity, or foreign dealers specifically, this response does not carry them — it gives the aggregate foreign, investment trust and dealer figures. Go to the official source for the breakdown. Using one aggregate definition throughout at least keeps your series internally consistent.
Every row carries its origin
Responses include provider, source_role and lineage. On measured rows, source_role reads official_twse_t86 and lineage records the endpoint name T86 along with the source authority and the payload date, which is what you need when reconciling against the official publication.
What we do not have
- No intraday institutional flow. This is post-close data and is not available during the session.
- No field that declares a row final. updated_at and as_of_date are the evidence you reason from; the official source itself has no fixed finalisation time.
- The buy and sell legs may be empty, as above. The net fields are the dependable set.
Common questions
1Why is my whole-market net always too large?
2The buy and sell fields are empty. Is the data broken?
3If I pull straight after the close, will the figures change later?
4How does this affect a backtest?
Want to query this data yourself? Create a free account.