Skip to main content
Experimental: these operations may change.
Risk runs need to be enabled for your account. All operations take your usual bearer token. The credential sets the owner. Do not send an owner in the body. 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.

Issuer bounds and country provenance

Compliance groups equal registered issuer identifiers and uses unique shared filer numbers to bridge legal entity identifiers. Provider identifiers must be dated on or before the report. Unresolved lines carry their absolute weight in the receipt. Unequal concentration bounds show at least and an upper bound; the verdict is unknown when the bounds straddle the limit. Mixed longs and shorts with unresolved weight have no verdict, and trim plans require exact positions. Issuers are legal entities; consolidated group limits need parent links. For a QQQ subject with SPY as comparator, each book’s country receipt states how much weight used the listing venue’s country because the company stated none.