Data by symbol: the semantic data API¶
The low-level API addresses a container: database/period/source/tablename, plus its type
and, for intraday data, its partition. The semantic data API addresses the data: bars of
EXAMPLE at 15 minutes since Monday. The server finds the source, the container and the
partitions, and says in the answer where the rows came from.
Three entry points run the same code (SharedData.DataAPI.service.dispatch):
| Entry point | Example |
|---|---|
| HTTP | GET /api/data/marketdata/EXAMPLE?period=M15&start=2026-10-05 |
| Client | ClientAPI.get_marketdata('EXAMPLE', period='M15', start='2026-10-05') |
| Local | shdata.dataframe('marketdata', 'EXAMPLE', period='M15', start='2026-10-05') |
This is a convenience, not the fast path. Each call resolves the dataset again and copies
the rows it returns. A producer, a backtest or a realtime consumer keeps using
shdata.table(), shdata.timeseries() and shdata.collection() directly.
It never writes: no row, no container, no index, no file.
Operations¶
| Route | Client method | Answers |
|---|---|---|
/api/data/sources |
get_data_sources |
sources, their kind, what each publishes |
/api/data/symbols |
get_data_symbols |
instruments one product actually holds |
/api/data/symbol/{symbol} |
get_data_symbol |
identity of an instrument, its spelling per product |
/api/data/marketdata/{symbol} |
get_marketdata |
bars and other market-data products; period=RT is the latest state |
/api/data/signals/{symbol} |
get_signals |
signal matrices and allocation tables |
/api/data/portfolios/{portfolio} |
get_portfolios |
performance, strategies, holdings |
/api/data/riskfactors/{factor} |
get_riskfactors |
factor metrics, optionally its positions |
/api/data/vol/{symbol} |
get_vol |
tenor, listed and strike surfaces, derived series |
Parameters are listed in the API docs (/apidocs, tag Data). A parameter that takes
several values (columns, metrics) accepts them comma-delimited (metrics=ret,open) or as
a repeated key (metrics=ret&metrics=open); the two forms can be mixed.
/api/data/symbols lists the instruments of the newest date of the dataset that would answer.
On a weekend that date holds only the small set of rows written then; date= asks for the instruments of
another day.
What every answer carries¶
{"provenance": {"dataset": "master/MarketData/M15/SRC/table/QUOTES", "partitions": ["2026"],
"source": "SRC", "source_kind": "consolidator", "store": "table", "semantics": "history",
"symbol": {"query": "EXAMPLE", "canonical": "EXAMPLE@XDEMO", "alias": "EXAMPLE@XDEMO", "alias_form": "canonical"},
"key": ["date", "symbol"], "fields": [{"name": "close", "dtype": "<f8", "unit": "price"}],
"row_provenance": "source", "range": {"start": "...", "end": "...", "defaulted": false},
"alternatives": [{"dataset": "...", "reason": "not needed"}], "rows": 2, "truncated": false},
"data": [{"date": "2026-10-05T13:30:00", "symbol": "EXAMPLE@XDEMO", "source": "DEMO", "close": 1.0}]}
row_provenance names the column that holds the vendor of each row in a consolidated source.
semantics is history for tables, timeseries and collections and latest for a cache: a
cache keeps the last state of each instrument and no history.
How a request is resolved¶
- Catalog. Every container the server can see is listed (names only, cached 60 s) and
classified by naming convention into a category and a product. A container no convention
covers stays visible as
uncategorized. - Symbol. The registry gives the instrument's identity; each dataset says how it spells
an instrument in its key, so the caller never builds
EXAMPLE_1MorEXAMPLE_20260116. - Source. The configured preference decides;
source=overrides it for one request. A store the configuration deprecates, or a table not written forstale_after_days, is tried last. When the preferred source has no rows for the request, the next one answers and the answer says so. - Partitions. The date range becomes partition names from the dataset's own format (none for daily data, a year for 15-minute data, a month for 1-minute data).
- Permissions. A token reads through these routes only what it can read through the low-level route of each container.
Limits¶
| Limit | Default | Configuration key |
|---|---|---|
| Rows of one answer | 10,000 (at most 100,000) | default_rows, max_rows |
Fields a listing shows per dataset (field_count has the total) |
50 | listed_fields |
| Range of a read with no symbol or entity | 31 days | scan_max_days |
| Range of a strike-surface read | 7 days | strike_max_days |
| Time of one collection query | 20 s | query_seconds |
A collection read always runs on the collection's key index. A collection without it is
refused with status 409; python -m SharedData.DataAPI.indexes reports which ones lack it and
--apply creates them (an operator's step: building an index on a large collection takes
hours).
Configuration¶
SHAREDDATA_DATA_API_CONFIG names a JSON file; docs/data-api-config.example.json is an
example. Without it no source is preferred and none is a consolidator. The file is read
again when it is replaced; while it is missing or cannot be read, the configuration read
last stays in effect and a warning is logged.
| Key | Meaning |
|---|---|
source_preference |
{"marketdata": ["SRC"], "vol/tenor": ["SRC"]}: sources to try first, per category or per category/product |
source_kinds |
{"SRC": "consolidator"}: kind of a source, over what its datasets suggest |
deprecated |
dataset paths to use only when nothing else answers; they do not decide the period of a request that names none |
stale_after_days |
a table not written for longer ranks last (30) |
registry_metadata, registry_collection |
the instrument master file and the provider-symbol collection |
canonical_fields, ticker_fields, alias_fields |
registry columns that hold the registry symbol, the ticker and vendor aliases |
riskfactor_sources |
source names of the factor namespace |
units |
{"field": "unit"} over the built-in units |