A generative engine for financ|

Sablier turns a single market timeline into thousands of distributionally consistent ones — so your backtests, signals, and risk models stop overfitting to the one path that happened.

Backed by:

01 / Starting Point

One history is all you get.

You only ever saw one draw from the distribution, and the markets that didn't happen are exactly where strategies break.

Backtests memorize the past.

A strategy tuned on one historical path learns that path's quirks, not a repeatable edge: brilliant in-sample, broken live. The textbook signature of overfitting.

One path under-weights the tail.

The stress in a single dataset only ever played out one way: one sequence, one set of correlations, hitting one set of positions. Your drawdown and tail-loss numbers price that single arrangement as if it were the only one the market could have dealt.

A single number hides the risk.

One backtest gives you one Sharpe, one max drawdown, one VaR. No confidence interval, no sense of how lucky that number was, a point estimate dressed up as certainty.

02 / The engine

A generative engine for markets.

Sablier learns the joint distribution of how your assets and macro factors move together, and generates thousands of paths statistically faithful to the real one, yet identical to none.

Learns the real dynamics.

The model captures how returns, volatility, and cross-asset relationships behave across market regimes: fat tails, volatility clustering, correlations that snap together in a crisis. Not a smooth Gaussian approximation, but the messy structure that actually shows up in markets.

Generates faithful alternatives.

From any starting state, it samples thousands of paths that preserve those statistical properties. Each one is a market that could have happened, a coherent path in its own right, not a reshuffle of the past or a noise process bolted onto a trend.

Conditioned on the regime you choose.

Pick a regime — a historical crash like COVID or the GFC, or today's regime (your last 200 bars). Sablier generates thousands of alternative versions of that exact window. You're not waiting for the scenario to happen again; you're producing as many examples of it as you need.

03 / Validation

We don't claim realism. We measure it.

A generator can only learn the distribution it's shown. The honest question is how faithfully it does so.

Tested against real market behavior.

Every path is scored against real history on the stylized facts that matter — fat tails, volatility clustering, the leverage effect, and crash co-movement. Twenty metrics across distribution, dependence, temporal structure, and drawdowns.

Calibrated, not just realistic.

Looking realistic isn't enough — the stated uncertainty has to hold. When the model says 90% of outcomes fall in a range, 90% do. Proper scoring rules — CRPS, PIT, interval coverage — measure that directly, not by eyeballing a fan chart.

Picks on synth survive on real.

The rankings you derive from synth have to carry over to real OOS data. A faithful generator keeps strategy ranks on synth tight to the ranks on real OOS — weaker ones don't, and the picks made on their synth fail on real.

See the validation in our public repo: github.com/sablier-ai/finval.

04 / Integration

Drop it straight into your stack.

The Sablier SDK gives you the generative engine as a few lines of Python — generate paths, condition on a regime, and pull the full distribution into your own backtests, notebooks, and risk pipelines.

Install:

pip install sablier-flow
then

Generate paths:

import sablier_flow as sf # Demo data is drop-in: df.attrs['data_types'] is pre-set df = sf.demo_data() backtest_window = df.iloc[-252:] # Your backtest (replace with your own) def my_backtest(prices): rets = prices['SPY'].pct_change().dropna() return {'sharpe': float(rets.mean() / rets.std() * (252 ** 0.5))} # Fit + generate 1,000 alternative paths fit = sf.fit(df, data_types=df.attrs['data_types'], horizon=252) paths = sf.generate(fit.model_id, n_paths=1000, data_types=df.attrs['data_types'], like=backtest_window) # Score robustness against your real backtest report = sf.robustness( my_backtest(backtest_window), [my_backtest(p) for p in paths.as_dataframes()], primary_metric='sharpe', ) print(report.summary())

Anyone can pass one backtest. Try a thousand.

Flip the hourglass.

Request access