Metadata-Version: 2.4
Name: neural-sde
Version: 0.6.1
Summary: Probabilistic price forecasting via stochastic differential equations (GBM/OU/neural), with AIC model selection, Monte Carlo quantiles, and fan charts.
Author: Kevin Downie
License: MIT
Project-URL: Repository, https://github.com/kdownie/neural-sde
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Office/Business :: Financial :: Investment
Classifier: Topic :: Scientific/Engineering :: Mathematics
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy<3,>=1.24
Requires-Dist: scipy<2,>=1.10
Requires-Dist: matplotlib<4,>=3.7
Provides-Extra: torch
Requires-Dist: torch<3,>=2.0; extra == "torch"
Provides-Extra: pandas
Requires-Dist: pandas<4,>=2.0; extra == "pandas"
Provides-Extra: demo
Requires-Dist: yfinance<2,>=1.0; extra == "demo"
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Dynamic: license-file

# neural-sde

Probabilistic price forecasting via stochastic differential equations. Fit a
GBM or OU (mean-reverting) model to a price series, generate Monte Carlo
forecasts with quantified uncertainty, and answer questions like "what's the
probability this is up 10% in 30 days?" — without pretending to know the
future.

> **This library produces probabilistic forecasts, not point predictions.**
> `forecast()` returns a distribution over future paths, not a single
> "the price will be X" answer. Treat every number it gives you — quantiles,
> `prob_above()`, the fan chart — as a statement about *uncertainty*, not
> a promise. Financial markets are not stationary; a model fit on last
> year's regime can be a poor guide to next month's.

## Install

```bash
pip install -e .                 # core: numpy, scipy, matplotlib
pip install -e ".[torch,pandas]" # + neural SDE path and pandas Series input
pip install -e ".[demo]"         # + yfinance, to run demo_forecast.py on real data
```

Requires Python 3.9+. See [requirements.txt](requirements.txt) /
[pyproject.toml](pyproject.toml) for pinned dependency ranges.

## Quickstart

```python
from neural_sde.highlevel import fit, forecast

model = fit(prices)                        # auto: AIC picks GBM vs OU
fc = forecast(model, horizon=30)           # antithetic Monte Carlo
print(fc.prob_above(prices[-1] * 1.10))    # P(+10% by day 30)
print(fc.quantiles([0.05, 0.5, 0.95]))     # 5th/50th/95th percentile price
fc.plot("forecast.png")                    # fan chart: history -> forecast cone
```

`prices` is any 1-D array-like or pandas Series of price levels (not
returns). `fit(model="neural")` routes to a torch-based `NeuralSDETrainer`
when torch is installed — see [Neural path](#neural-path) below before
using it for anything besides diffusion/volatility. The GBM/OU parametric
paths use exact (unbiased) transition densities and are the
production-quality default.

### On regimes

`fit()` estimates parameters from whatever window of history you give it. A
GBM fit on a 6-month bull run will forecast that drift forward; an OU fit on
a period of active mean-reversion will forecast reversion. Neither model
knows the regime changed the day after your training window ends. Re-fit
periodically, and sanity-check `model.summary()` (drift/vol/theta) against
what you'd expect for the asset before trusting the forecast.

### Neural path

`fit(model="neural")` learns state-dependent drift `f(x,t)` and diffusion
`g(x,t)` functions instead of GBM/OU's parametric forms.

**Diffusion recovery is solid** — verified within 0.4-14% of the true
value on synthetic GBM/OU calibration tests, and it's a genuine capability
the parametric paths don't have: state-dependent volatility, not a single
constant sigma. Useful for anything that wants to react to changing
vol/regime (option pricing, vol-aware risk sizing).

**Drift recovery is not usable at daily sampling frequency, and this
isn't a bug that more engineering fixes.** Recovering a state-dependent
drift function from a single daily-sampled price path hits a hard
signal-to-noise limit: SNR ≈ `mu * sqrt(dt) / sigma`, a few percent for
typical parameters. A 5-seed-averaged sweep from 2,000 to 20,000 daily
observations (up to 79 simulated years) improved median drift error only
from ~267% to ~135% — consistent with the expected slow `1/sqrt(n)`
convergence, and nowhere near a usable tolerance. Extrapolating that
rate, closing the gap would need on the order of 1,000+ years of daily
data. The parametric GBM/OU drift estimate doesn't have this problem: it
fits one global scalar averaged over the entire series rather than
attempting state resolution, which sidesteps the issue instead of
solving it.

**Practical guidance:** use the neural path for diffusion/volatility
modeling. For anything that needs a drift estimate — hedging deltas,
regime signals, mean-reversion speed — use the parametric GBM/OU drift,
not the neural path's `drift(x,t)`.

## What's inside

| Module | Purpose |
|---|---|
| [`highlevel.py`](highlevel.py) | Public API: `fit`/`forecast`/`FittedModel`/`Forecast` — start here |
| [`sde_core.py`](sde_core.py) | Core SDE theory: drift/diffusion, Ito<->Stratonovich conversion |
| [`solvers.py`](solvers.py) / [`torch_solvers.py`](torch_solvers.py) | Euler-Maruyama, Milstein, Heun solvers (numpy and torch backends) |
| [`multi_asset.py`](multi_asset.py) | Correlated multi-asset GBM/OU/CIR diffusion |
| [`neural_networks.py`](neural_networks.py) / [`trainer.py`](trainer.py) | Neural drift/diffusion networks and training loop — see [Neural path](#neural-path), requires torch |
| [`likelihood.py`](likelihood.py) | Score-matching / contrastive-divergence loss functions |
| [`forecasting.py`](forecasting.py) | Path forecasting and option pricing utilities |
| [`api.py`](api.py) | Lower-level unified solver entry point |

## Demo

```bash
pip install -e ".[demo]"
python demo_forecast.py
```

With `yfinance` installed and network access, edit the top of the script to
pull real data:

```python
import yfinance as yf
from neural_sde.highlevel import fit, forecast

prices = yf.download("AAPL", period="2y")["Close"]
model = fit(prices)
fc = forecast(model, horizon=30)
print(fc.summary())
fc.plot("forecast.png")
```

Offline, the script instead demos against **synthetic data with known
dynamics**, which doubles as a calibration test — it simulates GBM and OU
paths with known parameters, fits them back, and checks:

1. `fit()` recovers the true GBM parameters and AIC selects GBM.
2. `fit()` recovers the true OU parameters and AIC selects OU.
3. Monte Carlo quantiles agree with the exact lognormal distribution to <1%.
4. The `exact` and `euler` (core-library) engines agree on the median to <3%.

`forecast.png` and `forecast_example.png` in this directory are pre-generated
sample outputs from that script — regenerate them yourself with
`python demo_forecast.py`.

## Testing

```bash
pip install -e ".[dev]"
pytest test_convergence_rate.py test_adjoint_grad.py test_adjoint_verify.py \
       test_trainer_losses.py test_torch_solvers.py test_unified_entry.py \
       test_api_integration_v05.py test_edge_case_regression.py
```

`archive/` holds superseded tests kept for historical reference (see
[archive/README.md](archive/README.md)); they aren't part of the suite.

## License

[MIT](LICENSE).

## Credit

Built by Kevin Downie in collaboration with Claude (Anthropic).
