Send a saved portfolio you own with
portfolio: { portfolio_id }, or a what-if with portfolio: { holdings: [{ symbol, weight }] }. Set benchmark and, if needed, peers. See the portfolio guide for weights and limits.
Attribution and factor profit and loss use the latest book dated on or before
each period’s start. All saved portfolios, including one fund beside cash, use their dated holdings
snapshots, with the latest snapshot on or before each start and known by the
report date. Each start uses the last edit by the end of that day in New York.
A saved book with unknown values remains the opening book and withholds
unsupported figures; an older book never replaces it. The first saved date
supplies history coverage even when that first book has no known values.
Today’s holdings never stand in for those snapshots. A later book never fills a gap. If there is no such book,
the period is not covered: the section and tool reply say History starts <date>.
The requested period is kept and marked partial or unavailable. Start and end
books also exclude holdings dated after the report date, including reads with
a separate knowledge cutoff. See Monitor attribution
for how period starts and forward weight carries are measured. Row kinds and
diagnostics use report-date knowledge in both runtimes. A listed fund ends at
its own last supported close, even when its constituents have newer prices.
The saved portfolio period starts on its first saved date and reads
since first saved <date>; a standalone listed fund uses its first available
price and reads since inception. An unlisted basket without dated
history withholds this period with a reason. Missing factor returns, opening exposures
or listing closes do not hide a known first supported book date from the
section or reply. Figures stay withheld when their price endpoints are missing.
Keep one request_key for each run. Retry with the same key and request to join that run. Within 24 hours of its end, a changed request with that key returns 409. After 24 hours, the key starts a new run. At most three runs may run at once. A risk measure counts as one while it runs.
Each newly launched background job has a 60-minute budget, including data reads, coverage, computation and output. Initial validation, startup and stopping a timed-out job can add time before a terminal status is available. A run whose books hold names the model has not scored first waits up to 10 minutes while they are scored, within the same budget. Jobs already running retain their original deadline.
Read running again in a few seconds. A running run may carry progress:
An
ok or partial result includes summary, view, component and top-level analytics. The analytics reference gives schema version, artifact identity, raw and compressed sizes and SHA-256 hashes, and the exact expiry time.
The share-count check never changes which rows are valued, their weights, the
weight basis or whether the book is usable. The one count it sets aside is a
whole book’s: when that book’s counts disagree with its stated weights by more
than 10%, the engine stops using them, as described below.
The engine looks for the source’s valuation session before checking provider
share counts. Zero or missing market values supply no evidence against a share
count. For each listing currency with at least three distinct securities,
it compares the last five sessions at or before the holdings date, using each
listing’s price history and the exchange rate from the same session. A match
needs a median ratio within 0.5% of one and more than half its rows within 2%.
The closest median wins. Sessions within 0.1 percentage point are ambiguous;
the engine uses the later session and reports that ambiguity. Other currency
rows use their own qualifying session, or the book’s match where available.
At a matched session, a count that differs from its own market value in the
book’s currency by more than 2% becomes unverified. A foreign-currency group with
at least three rows clustered at its exchange rate also makes those clustered
counts unverified. Rows outside that cluster keep their own check results.
The receipt gives their count and stated weight with the reason
share count disagrees with
market value in the book’s currency. The weight test uses the share counts as given.
The engine never repairs a count.
If no session matches, the engine uses a 25% gross-error filter at the
holdings-date close. The receipt says the source’s valuation date was not
found and Counts passing that filter remain unverified. Disagreement,
gross-error filter and ambiguous-session receipts say The weight test uses
the share counts as given. Missing comparison data preserves existing behavior.
The currency-group check has gaps for fewer than
three rows and currencies within about 5% of the book’s currency.
If priced rows have share counts but the source states no
value currency for the book, those counts cannot be checked. The receipt says
“Share counts were not checked: the source states no value currency for this
book.” The same line adds “The stated weights were tested against these
unchecked counts.” when the counts were compared with the weights. No count-check
line is added when the counts did not affect the weight result, or when there
are no priced share-count rows. Row market values are not needed for this
unchecked-count receipt. The engine does not infer the book’s value currency from
the fund’s listing, net asset value currency or holding rows. This does not
change how values or weights are calculated.
The matched session checks counts only. Book values and the weight test still
use the holdings-date close; the weights themselves are the ones the book states. An earlier match does not prove the count across a later
split or holdings change; a marked break in the price history makes the row
fall back to the gross-error filter.
A fund book keeps the weights its source states. When its share counts and
closes are more than 10% from those weights, the book stays in use and the
receipt says share counts disagree with stated weights by X%; stated weights
kept. The engine never rebuilds weights from share counts, and uses that
book’s counts for nothing else, such as splitting a holdings change into trade
and price drift. Results from
earlier runs may still carry weights: "rebuilt_from_shares" and total_basis
in component.receipt.books[]; new results never do. A zero share count with a
nonzero stated weight counts as missing and does not enter the comparison; zero
shares with zero weight still means valued zero.
total_value is the source’s own total when the engine can use it. A book whose
counts agree with its weights can instead report the sum of its valued holdings,
unless a short holding could not be valued; a book whose counts disagree reports
only the source’s total. A parent book’s optional fund_total_notes lists each
distinct held fund book with a note as {symbol, holdings_date, note}. Older
results omit these fields. A book can carry total_note when its exposure basis
is not known.
A derivative without a proven contra offset never causes a book to be refused. Every row remains visible. The receipt carries exposure_unknown_reason: "a derivative's exposure is not known" with total_note, which labels every stated weight % of the provider’s total. A negative derivative figure has unknown direction; it is not a short position.
Valuation coverage excludes these derivative figures from its denominator,
because share counts and prices cannot value them. They keep their stated
weights.
For such a book, exposure, long and short sides, net exposure and leverage are not known. The note bounds gross exposure below by the other rows’ sizes. Factor and risk sections, including attribution and factor profit and loss, are unavailable with that reason. Book-level liquidity aggregates are null; measured days to sell each holding remain. Position-limit figures remain, with no bands, passes or trim plans; a breach is shown only when the figure alone exceeds the limit. Prices, the fund’s own performance, valued-share multiples and share sector and country weights remain available. Proven future/contra pairs keep their existing treatment.
Historical risk measurement keeps an accepted book with an unsettled basis. Its exposure, risk and active are null, its gross coverage and look-through weights are null, and a warning gives the reason. Counts and fund-book provenance remain; no numeric zero substitutes for missing exposure.
When either book has an unsettled derivative basis, active share and weight in
common are null with the same reason. Row weights and share classifications
remain available.
If only a benchmark or peer has this unknown basis, the subject’s own total
volatility and absolute risk measures remain available. The risk section is
partial: active risk, tracking error and active exposures against that book are
withheld with the reason. An unsettled subject still has no exposure-dependent
risk figures.
Issuer limits include bonds and other issuer-bearing holdings alongside shares.
A derivative’s stated figure also appears in position limits: a 60% figure
against a 50% limit proves a breach of 10 percentage points, while its unknown
exposure basis still prevents a band, pass verdict or trim plan.
view.unmodelled[] lists the subject’s gaps by size, with symbol and weight; unmodelled_count counts all of them. Both read the current model, which only the risk analysis does: a run without it has an empty list, a null count and no coverage. view.coverage[] names gaps in the subject, benchmark and peers, with book, symbol, signed weight, reason_code and reason. A holding with no usable value has weight: null, code invalid_inputs, and reason “This holding could not be valued; its risk is excluded.” Null means its weight is not known. Weighted gaps sort before unvalued gaps in the subject list.
Depositary receipts count as equity, priced on their own listing. If a holding’s
own listing has no reference type, the engine uses other listings with the same
checked ISIN only when every available type agrees. It keeps the holding’s
original listing. Missing or conflicting evidence keeps the holding’s existing
classification fallback.
An accounting balance needs its own stated currency to earn a known zero return.
Without one, it stays a named source gap with its signed weight in uncovered or
unmodelled coverage. History and attribution give it no zero-return leg. The
engine does not fill its currency from the book or the report.
The trust and risk_change sections check the last stated holdings list dated on or before each past model publication against that publication’s model, looking through held funds where their holdings are available. A list may predate the publication by several months and serve more than one publication. Their receipt lines use past_unmodelled for names that a past publication’s model did not cover, whether held directly or through a fund. These gaps do not add names to today’s view.unmodelled or its count. Gaps with a specific cause, such as cash_currency or no_fund_book, keep that reason code and name the past holdings list in their reason.
A saved book with unvalued lines returns a partial risk section and measures its valued lines. Each valued weight is its value over valued net assets, cash included and shorts negative. The book’s warning gives the number of unvalued lines and says that shares and exposures, including the modelled share of gross, describe the valued lines. A book with no valued line or no positive valued net assets is refused.
The valuation section lists trailing price to earnings first, then forward
price to earnings. Its headline is Trailing P/E premium, keyed by
valuation.premium.trailing_pe. Trailing earnings cover the last twelve months.
Forward earnings and expected earnings growth use the twelve-month blends in
MSCI’s Fundamental Data Methodology. Losses keep their sign; growth has no
figure when the book’s summed backward earnings are zero or negative. The data
receipt names missing inputs and per-share figures whose currency cannot be
matched to the price.
For example, in the complete analytics, sections.valuation.data.books holds one
entry per book; the subject book’s entry has a null comparator_key, and its
measures.trailing_pe gives the trailing multiple and coverage. A book with no consensus
estimates can still publish trailing price to earnings and its premium.
A null figure was not computed; it does not mean zero. A failed run returns 200 with failure_code and reason, plus details for an engine fault:
Finished results and complete analytics stay available for 24 hours after the run ends, including after the sandbox sleeps.
GET /risk/runs/{run_id}/artifact returns the schema-version-2 document itself as application/json, with its exact Content-Length and raw SHA-256 as a quoted ETag. Unknown, foreign and expired runs share a 404 response. Before publication finishes, the route returns 409 { "status": "not_ready" }.
To copy large documents, send Accept: application/gzip. This selects the stored gzip bytes, their length and their SHA-256 ETag. Add Range: bytes=0-4194303 to request at most 4 MiB; a valid range returns 206 with Content-Range. Continue through the compressed length in analytics.stored.size_bytes, then verify the compressed size and hash, decompress, and verify the raw size and hash from analytics. Invalid ranges, ranges outside the stored bytes and ranges over 4 MiB return 416. Each range rechecks the owner and expiry. To resume a copy you started earlier, send its ETag in If-Range; when the stored bytes have changed, the route returns 200 with the whole gzip file instead of a range, so start that copy again.
Publication accepts at most 256 MiB raw and 64 MiB compressed. The engine calculates once, writes immutable files and uploads 5 MiB parts. It verifies both stored and decompressed hashes and lengths before publishing a reference. A build, validation, size or storage failure fails the run with analytics_publication_failed; it does not return a successful summary without complete output. Retries reuse those files and never repeat the calculation. Old retained results and old artifact bodies keep their earlier format until they expire; readers must deploy before new writers, and rollback must stop new writers before rolling back readers.
Cancellation, timeout and container loss leave a known terminal outcome. A late result cannot replace a cancellation or a newer attempt. Store outages return an unavailable error rather than pretending the run does not exist. Owner-scoped storage expires reads after 24 hours. The bucket keeps its two-day deletion rule, allowing up to one more day for provider deletion, and aborts unfinished multipart uploads after one day. Completed data deletes within three days.
On 503, retry the same call with the same key. If cancellation could not stop the process, retry the cancellation call with the same
run_id; a retained cancellation does not prevent another stop attempt. See the generated endpoint reference for the full request and response schemas.