Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 7 additions & 10 deletions app/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
from fastapi.staticfiles import StaticFiles
from fluid.utils import log

from app.api.mcp import create_mcp as _create_mcp
from app.utils.paths import APP_PATH
from quantflow import __version__

Expand Down Expand Up @@ -56,17 +55,15 @@ async def api_redoc() -> HTMLResponse:
title="Quantflow API",
)

api.include_router(cointegration_router)
api.include_router(heston_router)
api.include_router(hurst_router)
api.include_router(rates_router)
api.include_router(sampling_router)
api.include_router(smoother_router)
api.include_router(volatility_router)
api.include_router(cointegration_router, tags=["timeseries"])
api.include_router(heston_router, tags=["pricing"])
api.include_router(hurst_router, tags=["statistics"])
api.include_router(rates_router, tags=["pricing"])
api.include_router(sampling_router, tags=["statistics"])
api.include_router(smoother_router, tags=["timeseries"])
api.include_router(volatility_router, tags=["pricing"])
app.include_router(api)
app.include_router(status_router, include_in_schema=False)
if _create_mcp is not None:
api.mount("/mcp", _create_mcp().streamable_http_app())
examples_dir = APP_PATH / "examples"
if examples_dir.is_dir():
app.mount(
Expand Down
9 changes: 7 additions & 2 deletions app/api/cointegration.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from pydantic import BaseModel, Field
from statsmodels.tsa.vector_ar.vecm import coint_johansen

from app.api.docs import load_description
from quantflow.data.fmp import FMP

from .deps import FMPDep, RedisCache, RedisDep
Expand All @@ -20,13 +21,17 @@ class CointegrationResponse(BaseModel):
)


@cointegration_router.get("/cointegration")
@cointegration_router.get(
"/cointegration",
summary="BTC/ETH/SOL cointegration",
description=load_description("cointegration.md"),
)
async def cointegration(
fmp: FMPDep,
redis: RedisDep,
frequency: FMP.freq = Query(
FMP.freq.daily,
description="Price frequency",
description="Price sampling frequency.",
),
) -> CointegrationResponse:
cache = RedisCache(
Expand Down
7 changes: 7 additions & 0 deletions app/api/docs/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from pathlib import Path

_DOCS_PATH = Path(__file__).parent


def load_description(filename: str) -> str:
return (_DOCS_PATH / filename).read_text(encoding="utf-8")
9 changes: 9 additions & 0 deletions app/api/docs/cointegration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Johansen cointegration analysis of BTC, ETH, and SOL log prices.

Returns the mean-reverting spread (residuals) and the cointegrating vector
(eigenvector for the largest eigenvalue of the Johansen test).

Log prices are standardised before the test; the returned cointegrating vector
is rescaled back to the original units and normalised to unit length.
A residual near zero means the spread is close to its long-run equilibrium.
Large positive or negative residuals signal potential mean-reversion trades.
11 changes: 11 additions & 0 deletions app/api/docs/double_exponential_sampling.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Sample from a double exponential (asymmetric Laplace) distribution and compare
the empirical PDF to both the analytical PDF and the PDF recovered from the
characteristic function.

The double exponential distribution models asymmetric jump sizes in jump-diffusion
models. The asymmetry is controlled by `log_kappa`: positive values produce
heavier right tails (upward jumps more likely), negative values heavier left tails.
At `log_kappa = 0` the distribution is symmetric.

The characteristic function inversion provides an independent check that the
analytical and numerical PDFs agree.
12 changes: 12 additions & 0 deletions app/api/docs/gaussian_sampling.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Sample from a Vasicek mean-reverting Gaussian process and compare the empirical
distribution to the analytical marginal PDF.

The process is run for one unit of time with 1000 time steps. The terminal
distribution is binned into 50 bins and compared against the analytical Gaussian PDF.

A Kolmogorov-Smirnov test is included to quantify goodness of fit.
A high p-value (e.g. above 0.05) means the simulation is statistically consistent
with the analytical distribution.

Antithetic variates halve the variance of Monte Carlo estimates by pairing each
draw with its negative, without additional model evaluations.
15 changes: 15 additions & 0 deletions app/api/docs/heston_vol_surface.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Generate a theoretical implied volatility surface using a stochastic volatility model.

Two models are available:

- **jd** (Jump Diffusion): a Variance-Gamma-style model with a double-exponential
jump distribution. Controlled by `vol`, `jump_fraction`, `jump_intensity`, and
`jump_asymmetry`. The `kappa` and `rho` parameters are ignored.

- **hj** (Heston with Jumps): the Heston stochastic volatility model augmented with
double-exponential jumps. All parameters are used.

The surface is returned as a grid: 10 maturities evenly spaced from 0.1 to 1.0 years,
and 51 moneyness values from -0.5 to 0.5 (log-moneyness scaled by sqrt(ttm)).
Implied volatilities that cannot be computed (e.g. deep in/out of the money) are
returned as 0.
9 changes: 9 additions & 0 deletions app/api/docs/hurst_vasicek.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Simulate a Vasicek mean-reverting process and estimate its Hurst exponent.

The Vasicek process reverts to zero at speed `kappa`. Higher values of `kappa`
produce stronger mean reversion and a lower Hurst exponent (well below 0.5).
Low values of `kappa` approach Brownian motion (Hurst near 0.5).

The Hurst exponent is estimated from both realized variance and three OHLC-based
estimators (Parkinson, Garman-Klass, Rogers-Satchell) sampled at periods of
10min, 20min, 30min, and 1h.
15 changes: 15 additions & 0 deletions app/api/docs/hurst_wiener.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Simulate a Wiener (Brownian motion) process over one day at one-second resolution
and estimate its Hurst exponent using multiple methods.

The Hurst exponent measures long-range dependence in a time series.
A value of 0.5 indicates pure Brownian motion (no memory).
Values above 0.5 indicate trending behaviour; values below 0.5 indicate mean reversion.

Three OHLC-based range estimators are computed across multiple sampling periods:

- **Parkinson**: uses high-low range.
- **Garman-Klass**: uses open, high, low, close.
- **Rogers-Satchell**: drift-adjusted version of Garman-Klass.

The Hurst exponent for each estimator is derived from the slope of log(variance)
vs log(sampling period).
10 changes: 10 additions & 0 deletions app/api/docs/poisson_sampling.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Sample from a Poisson jump process and compare the empirical jump count distribution
to the analytical Poisson PMF.

The process is run for one unit of time with 1000 time steps. Jump counts are
binned and compared against the analytical distribution using a chi-squared
goodness-of-fit test. Tail bins with expected count below 5 are merged to satisfy
the chi-squared validity requirement.

A high p-value (e.g. above 0.05) means the simulation is statistically consistent
with the analytical Poisson distribution.
12 changes: 12 additions & 0 deletions app/api/docs/supersmoother.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Apply SuperSmoother and EWMA filters to two years of historical daily price data
for a given symbol.

SuperSmoother is an adaptive low-pass filter designed by John Ehlers. It suppresses
high-frequency noise more aggressively than EWMA while introducing less lag at
trend inflections.

Both filters use the same `period` parameter, making it easy to compare their
smoothing characteristics on the same data. Shorter periods follow price more
closely; longer periods produce smoother output with more lag.

Supported symbols include crypto pairs (e.g. BTCUSD, ETHUSD) and US equities.
21 changes: 21 additions & 0 deletions app/api/docs/volatility_surface.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
Fetch the live implied volatility surface for a crypto or equity asset and return
the calibrated surface with discount curves and forward prices.

Data sources:

- **BTC, ETH**: live option chain from Deribit (inverse, crypto-quoted contracts).
- **SPY, AAPL, NVDA**: option chain from Yahoo Finance (standard equity contracts).

The surface is calibrated using Black-Scholes implied volatilities. Outlier options
(bid-ask spread too wide, or IV outside plausible bounds) are disabled before
the surface is returned.

Two discount curves are calibrated from the option data:

- **quote_curve**: discount curve for the numeraire (USD for equity, crypto for inverse).
- **asset_curve**: discount curve for the underlying asset.

The forward curve and per-maturity implied forwards from put-call parity are also
included, which are useful for detecting curve arbitrage or funding dislocations.

Responses are cached; live data may be up to a few minutes old.
17 changes: 17 additions & 0 deletions app/api/docs/yield_curve.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
Fit a yield curve model to a set of (time-to-maturity, rate) pairs and return
the calibrated curve plus interpolated rates.

Four curve types are supported:

- **nelson_siegel**: the Nelson-Siegel parametric model, well suited for smooth
yield curves with a single hump. Good default choice.
- **cir_curve**: Cox-Ingersoll-Ross model, a mean-reverting short-rate model.
- **vasicek_curve**: Vasicek model, a Gaussian mean-reverting short-rate model.
- **no_discount**: flat discount curve (no discounting), useful as a baseline.

Rates should be continuously compounded and expressed as decimals (e.g. 0.045 for 4.5%).
Time to maturity is in years (e.g. 0.25 for 3 months, 2.0 for 2 years).

If `max_ttm` is provided, the response includes `num_points` interpolated rates
evenly spaced up to that maturity, which is useful for plotting a smooth curve.
Otherwise, rates are returned at the input maturities only.
7 changes: 6 additions & 1 deletion app/api/heston.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from fastapi import APIRouter, Query
from pydantic import BaseModel, Field

from app.api.docs import load_description
from quantflow.options.pricer import OptionPricer
from quantflow.sp.heston import HestonJ
from quantflow.sp.jump_diffusion import JumpDiffusion
Expand All @@ -18,7 +19,11 @@ class VolSurfaceGridResponse(BaseModel):
)


@heston_router.get("/heston-vol-surface")
@heston_router.get(
"/heston-vol-surface",
summary="Theoretical implied volatility surface",
description=load_description("heston_vol_surface.md"),
)
async def heston_vol_surface(
model: str = Query(
"jd",
Expand Down
13 changes: 11 additions & 2 deletions app/api/hurst.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from fastapi import APIRouter, Query
from pydantic import BaseModel, Field

from app.api.docs import load_description
from quantflow.sp.ou import Vasicek
from quantflow.sp.wiener import WienerProcess
from quantflow.ta.ohlc import OHLC
Expand Down Expand Up @@ -86,7 +87,11 @@ def _ohlc_hurst(df: pd.DataFrame, serie: str, periods: tuple) -> dict[str, float
return result


@hurst_router.get("/hurst-wiener")
@hurst_router.get(
"/hurst-wiener",
summary="Hurst exponent of a Wiener process",
description=load_description("hurst_wiener.md"),
)
async def hurst_wiener(
sigma: float = Query(2.0, description="Wiener process volatility", ge=0.1, le=10),
) -> HurstWienerResponse:
Expand Down Expand Up @@ -138,7 +143,11 @@ async def hurst_wiener(
)


@hurst_router.get("/hurst-vasicek")
@hurst_router.get(
"/hurst-vasicek",
summary="Hurst exponent of a Vasicek process",
description=load_description("hurst_vasicek.md"),
)
async def hurst_vasicek(
kappa: float = Query(10.0, description="Mean reversion speed", ge=1.0, le=500.0),
) -> HurstVasicekResponse:
Expand Down
90 changes: 0 additions & 90 deletions app/api/mcp.py

This file was deleted.

7 changes: 6 additions & 1 deletion app/api/rates.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from fastapi import APIRouter, Query
from pydantic import BaseModel, Field

from app.api.docs import load_description
from quantflow.rates import AnyYieldCurve, YieldCurve

rates_router = APIRouter()
Expand All @@ -17,7 +18,11 @@ class YieldCurveResponse(BaseModel):
rates: list[float] = Field(description="List of rates for the fitted curve")


@rates_router.get("/yield-curve")
@rates_router.get(
"/yield-curve",
summary="Fit and interpolate a yield curve",
description=load_description("yield_curve.md"),
)
async def yield_curve(
ttm: list[float] = Query(
..., description="List of time to maturities corresponding to the rates"
Expand Down
Loading
Loading