What if? Simulating markets from their own history
"What happens to my book if NVIDIA drops ten percent?" is one of the most common questions on a desk and one of the hardest to answer well. The usual answers are a beta, which is a model, or a story, which is a memory. Neither tells you what actually happened the last twenty times.
EquationDB now has a statement for that question. You set a behaviour for one or more instruments, choose what to read it on, and the database answers from stored history, with the sample size next to every number.
SIMULATE NVDA -5% ON top500 FWD 1d, 5d, 20d
This article explains what comes back, how the past cases are chosen, and the places where the statement declines to give you a number.
What a simulation is here
A scenario is a set of building blocks joined with AND. Each block is read on its own instrument:
- a move:
NVDA -5%, or over several sessions,SPY -2% OVER 3d - at least a move:
USDJPY -3% OR MORE OVER 1w - an explicit band:
NVDA BETWEEN -12% AND -8% - a level reached:
$VIX TO 25 - a state:
$VIX > 18
Any stored instrument can be in a scenario: stocks, funds, the volatility index, currency pairs, crypto, a bond fund standing in for rates, a theme index such as $TH.AI. The target is whatever you want to read it on: a universe, a sector or theme, one symbol, a portfolio by weights, or the result of another statement.
The answer has one row per horizon. The first, during, is the target's return over the scenario's own window: the co-move. The others are measured from the close of the scenario session: what followed. Each row has the number of past cases, the mean and median, four percentiles, the hit rate, the worst and best case, and the same target's return over every session in the window as a baseline. Markets drift up; a response only means something next to what the target does anyway.
The dates are part of the answer
A distribution built from fourteen days should be read as fourteen days. So the list is one clause away:
SIMULATE NVDA -5% ON top500 FWD 1d, 5d, 20d VIEW analogues
Each row is one past case: the date, the move that was measured, how many sessions matched, and the response at every horizon. If most of the dates fall in one year, the result describes that year, and the caveats say so.
How the past cases are chosen
The matching rules are fixed and reported with every result.
A band, not a point. NVDA -5% matches sessions on which NVDA's return was within a quarter of the move either way: −6.25% to −3.75%. TOLERANCE changes the width.
Adjacent days are one episode. A sell-off that matches three sessions running is one event, not three. Matching sessions within five sessions of the previous one are grouped, and the first is the case that is used: choosing the biggest day of a cluster would require knowing how the cluster ended.
No hindsight. A session is matched from closes up to and including that session, and nothing later can add or remove a case.
Overlap is counted. Two cases ten sessions apart share half of a twenty-session window, so each row reports n_independent beside n.
Too few is an error. With fewer than eight cases the band is widened once to ±35% of the move and once to ±50%, and the result says that it was. If there are still too few, the statement fails with the count it found:
SIMULATE NVDA -30% ON top500
That returns TOO_FEW_ANALOGUES with the number of cases, the band that was tried and what you can do instead. An average of three days would look like a finding. It is not one, so it is not printed. MIN 3 lets you read the few dates yourself; below three the statement never answers.
Several instruments, and a target from the graph
Scenarios can combine instruments, and the target can be a query:
SIMULATE SPY -2% OVER 3d AND $VIX > 18 ON (GRAPH NEIGHBORS OF NVDA ACROSS supplies) FWD 5d, 20d
That reads a three-session market slide, with volatility already elevated, on NVIDIA's stated customers and suppliers. A regime condition narrows a scenario further:
SIMULATE $TH.AI -5% OVER 5d WHEN drawdown < -5
ON theme('TH.AI') FWD 5d, 20d MIN 5
Here the AI theme index falls about 5% in a week while already more than 5% below its high. Every extra condition shrinks the sample, and the count shows it.
A portfolio
Weights go in, and the response comes back for the portfolio and for each holding:
SIMULATE NVDA -5% PORTFOLIO (AAPL 0.4, MSFT 0.6) FWD 5d VIEW contributors
Each holding has its weight, its mean response, its contribution (weight times mean) and its worst past case with the date. A negative weight is a short.
The model-implied response, kept apart
History is one kind of evidence. A beta is another, and it is always available, even for a move with no precedent. SIMULATE computes it too: a least-squares beta of each target on the shocked instruments over the last 250 sessions, times the size of the move.
SIMULATE NVDA -10% ON top500 METHOD model LIMIT 20
The two are never blended. The historical views carry model_implied_pct as a separate column on the during row only, because a beta describes co-movement during a move and says nothing about what follows. The model view puts the beta, its fit and the historical co-move side by side; where they disagree is often the interesting part.
The result also says how large the move is, in standard deviations of the instrument's recent sessions and, where options data exists, in option-implied ones. A five-sigma scenario with nine precedents reads differently from a two-sigma one with two hundred.
Paths
Averages hide the journey. A basket that is flat on average twenty sessions later may have been 8% lower on the way. The paths view answers that with a bootstrap:
SIMULATE NVDA -5% ON top500 FWD 20d PATHS 1000 SEED 7 VIEW paths
Each path is built five sessions at a time: for sessions one to five it draws one past case and uses what the target did on those sessions after it, then draws again for six to ten. What tends to happen on day one stays on day one, while the cases are mixed. The result is a fan of percentiles for each session.
SIMULATE NVDA -5% ON top500 FWD 20d PATHS 1000 SEED 7 THRESHOLD -5%, -10% VIEW probabilities
This reports the share of paths that touched each threshold and the share that ended beyond it, next to the same shares for the past cases themselves. The seed is part of the statement, so a colleague can reproduce your numbers exactly.
A bootstrap re-arranges what happened after past matches. It cannot show an outcome that never occurred, and with a dozen cases its fan is narrow for that reason alone.
It is a table
Like every other result in EquationDB, a simulation is rows and columns, so it can sit inside SQL:
SELECT horizon, n, round(median_pct, 2) AS median, round(edge_pct, 2) AS edge FROM (SIMULATE USDJPY -3% OVER 1w ON refs)
In the app
The Simulations page builds the statement from a form and shows the response table, the fan of paths, the distribution against the baseline, the timeline of past cases and, for a portfolio, the contributors. Every view has Open in Query and Save as app. Four sample apps with a move-size slider and a What-if desk dashboard are under Apps and Dashboards.
What it is not
It is not a forecast. Every number describes what followed past matches of a scenario, in a sample whose size is on the same row. A universe is today's list of members, so names that no longer trade are missing from old cases. Returns are close to close, without dividends or costs.
What it gives you is a faster, more honest first answer to "what if?": the cases, their spread, the baseline, and a clear line between what happened and what a model implies.
The full reference is on the SIMULATE page.