Skip to main content

scivol

Fast, accurate volatility models for time series in Python.

scivol is a Python library for likelihood-based volatility modeling with GARCH-family models. It provides composable model specifications such as GARCH(1, 1) + StudentT() and ARMA(1, 1) + GARCH(1, 1) + SkewT(). scivol uses analytical gradients and Hessians for maximum speed and accuracy, and verifies those derivatives against automatic differentiation. It also provides MLE and QMLE estimation, diagnostic tests and automatic model selection.

Supported models

  • Univariate volatility models: GARCH(p, q), GJRGARCH(p, q), EGARCH(p, q) (Normal, StudentT, SkewT, and GED surfaces)
  • Conditional densities: Normal(), StudentT(), SkewT(), GED()
  • Mean model: ARMA(p, q) (standalone with Normal() and GED())
  • Composite univariate specifications: volatility-only models and ARMA(p, q) + GARCH(P, Q) with any shipped linked density, plus ARMA(p, q) + EGARCH(P, Q) for Normal(), StudentT(), SkewT(), and GED()
  • Exogenous-mean specifications: standalone ARX/HARX with Normal(), StudentT(), SkewT(), and GED(), plus linked ARX/HARX + GARCH(P, Q), ARX/HARX + GJRGARCH(P, Q), and ARX/HARX + EGARCH(P, Q) with the same shipped densities
  • Multivariate correlation model: DCC(p, q) with Gaussian or common-df Student-t copula dynamics
  • Automatic model selection helpers: AutoVol() and AutoDensity()

Performance

Scivol consistently outperforms other libraries in both speed and optimality. Below are benchmarks on synthetic data. Unless noted, each row uses 1,000,000 observations.

GARCH(1,1)-Normal:

Library Time Converged Reported log-likelihood
scivol 0.300 s Yes -1,379,431.12
Python arch 8.0.0 1.951 s Yes -1,379,777.40
MATLAB R2024a garch(1,1) 3.400 s Yes -1,379,432.00
R rugarch 1.5.5 24.029 s Yes -1,379,432.00
Manual Python 6.659 s Yes -1,383,160.89

GJR-GARCH(5,5)+Student-t:

Library Time Converged Reported log-likelihood
scivol 7.583 s Yes -844,600.85
Python arch 8.0.0 27.267 s Yes -844,604.00
MATLAB R2024a gjr(5,5) 44.125 s Yes -844,604.00
R rugarch 1.5.5 477.904 s Yes -844,605.57

AR(1)+EGARCH(2,1)+Normal:

Library Time Converged Reported log-likelihood
scivol 1.895 s Yes -318,924.29
Python arch 8.0.0 9.792 s Yes -318,925.65
MATLAB R2024a arima + egarch 83.554 s Yes -318,925.47
R rugarch 1.5.5 121.280 s Yes -318,925.49

Install

pip install scivol

Prebuilt wheels are the default installation path on supported platforms. If no compatible wheel is available, pip falls back to building from the source distribution, which requires a working C toolchain. tqdm ships as a normal dependency, so progress bars are available out of the box.

Quick start

import numpy as np
from scivol import GARCH, Normal, StudentT

np.random.seed(42)
returns = np.random.randn(1000) * 0.01

spec = GARCH(1, 1) + Normal()
result = spec.fit(returns)
result.summary()

For workflows that iterate over many fits, progress bars are enabled by default. You can disable them globally:

import scivol

scivol.settings.show_progress = False

Output:

══════════════════════════════════════════════════════════════════════
                    GARCH Model Estimation Results                    
══════════════════════════════════════════════════════════════════════
Model:       GARCH(1,1)+Normal
Method:      MLE
Date:        2026-01-30 15:42:38
──────────────────────────────────────────────────────────────────────
No. Observations:    1000            Converged:      Yes
No. Parameters:      3               Iterations:     42
Time Elapsed:        0.030s
──────────────────────────────────────────────────────────────────────
Log-Likelihood:            3456.7890
AIC:                      -6907.5780
BIC:                      -6892.8560
──────────────────────────────────────────────────────────────────────

                         Parameter Estimates                          
──────────────────────────────────────────────────────────────────────
Parameter            Coef      Std Err     t-stat      P>|t|
──────────────────────────────────────────────────────────────────────
omega          1.2345e-06    2.34e-07      5.27    <0.001
alpha[1]           0.0523      0.0084      6.23    <0.001
beta[1]            0.9412      0.0092    102.30    <0.001
──────────────────────────────────────────────────────────────────────

                          Model Diagnostics                           
──────────────────────────────────────────────────────────────────────
Persistence (alpha + beta): 0.993500
Stationary:               Yes
Unconditional Variance:   1.900000e-04
Half-life (periods):      106.3
══════════════════════════════════════════════════════════════════════

Contents

  1. Performance
  2. Model specification
  3. Components
  4. Estimation
  5. Automatic model selection
  6. Results
  7. Display settings
  8. Diagnostics
  9. API reference

Model specification

Build models by combining components with +. scivol orders them automatically: MEAN, then VOLATILITY, then DENSITY.

from scivol import EGARCH, GARCH, GJRGARCH, ARMA, DCC, Normal, StudentT, SkewT

spec = GARCH(1, 1)                          # Normal density by default
spec = GARCH(1, 1) + StudentT()             # Explicit density
spec = GJRGARCH(1, 1) + StudentT()          # Asymmetric volatility
spec = EGARCH(2, 1) + Normal()              # Log-variance volatility model
spec = ARMA(1, 1) + GARCH(1, 1) + SkewT()  # Mean + volatility + density
spec = ARMA(1, 1) + EGARCH(2, 1) + Normal() # Linked mean + EGARCH volatility
dcc = DCC(1, 1)                             # Dynamic correlations (multivariate)

One component per role. If you omit the density, Normal() is added for you.

Alternative operators produce the same result:

spec = garch + normal      # __add__
spec = garch < normal      # __lt__
spec = garch << normal     # __lshift__
spec = normal >> garch     # __rlshift__

Components

GARCH(p, q)

Conditional variance:

$$ \sigma_t^2 = \omega + \sum_i \alpha_i \varepsilon_{t-i}^2 + \sum_j \beta_j \sigma_{t-j}^2 $$

Stationary when $\sum \alpha + \sum \beta < 1$.

spec = GARCH(1, 1)  # most common
spec = GARCH(2, 1)

Parameters: omega ($\omega > 0$), alpha[1:p] (ARCH terms), beta[1:q] (GARCH terms).

GJRGARCH(p, q)

Adds a leverage term so that negative shocks raise volatility more than positive shocks of the same size:

$$ \sigma_t^2 = \omega + \sum_i \left(\alpha_i + \gamma_i I(\varepsilon_{t-i} < 0)\right)\varepsilon_{t-i}^2 + \sum_j \beta_j \sigma_{t-j}^2 $$

A negative shock contributes $(\alpha + \gamma)\varepsilon^2$ to the next period's variance; a positive shock contributes $\alpha \varepsilon^2$.

Stationarity:

  • Symmetric densities (Normal, Student-t): $\alpha + 0.5\gamma + \beta < 1$
  • Asymmetric densities (Skew-t): $\alpha + \gamma P(z < 0) + \beta < 1$
spec = GJRGARCH(1, 1) + StudentT()

EGARCH(p, q)

Log-variance volatility dynamics:

$$ \log h_t = \omega + \sum_{i=1}^{p}\alpha_i(|z_{t-i}| - \mathbb{E}|z|) + \sum_{i=1}^{p}\gamma_i z_{t-i} + \sum_{j=1}^{q}\beta_j \log h_{t-j} $$

spec = EGARCH(1, 1) + Normal()
spec = EGARCH(2, 1) + StudentT()

EGARCH is currently shipped for Normal(), StudentT(), SkewT(), and GED().

Parameters: omega, alpha[1:p], gamma[1:p] (leverage), beta[1:q].

DCC(p, q)

Dynamic Conditional Correlation for multivariate return series.

DCC.fit() uses a two-step workflow:

  1. Fit a univariate volatility model to each series.
  2. Fit Gaussian or Student-t copula DCC dynamics to the standardized residuals.
from scivol import DCC, GARCH, StudentT

dcc = DCC(1, 1)
result = dcc.fit(returns_df, univariate_spec=GARCH(1, 1) + StudentT())

t_dcc = DCC(1, 1, distribution="studentt")
t_result = t_dcc.fit(returns_df, univariate_spec=GARCH(1, 1) + StudentT())
t_result.params.nu

Result access focuses on the economically useful correlation objects:

  • result.Rt for the full time-varying correlation path
  • result.corr(i, j) for a single pair
  • result.unconditional_corr for the long-run correlation matrix
  • result.forecast_correlation(horizon) for the forecast correlation path
  • result.forecast(horizon) for joint marginal and correlation forecasts

Draw many outcomes from one unchanged forecast origin with scenarios():

scenarios = result.scenarios(50_000, seed=7)
empirical = result.scenarios(50_000, seed=7, innovations="empirical")

The fitted DCC distribution supplies the Gaussian or Student-t copula. The parametric mode maps its draws through each fitted marginal density; the empirical mode uses standardized-residual quantiles. simulate() uses the same copula and marginals along an evolving time path.

Rolling DCC fits warm-start both the marginals and correlation dynamics:

rolling = dcc.fit_rolling(
    returns_df,
    window=1000,
    step=20,
    univariate_spec=GARCH(1, 1) + StudentT(),
    n_jobs=4,
)

ARMA(p, q)

Conditional mean. Standalone ARMA(p, q) is supported with Normal() and GED(). Linked composite mean-volatility fits currently ship for ARMA(p, q) + GARCH(P, Q) with Normal(), StudentT(), SkewT(), and GED(), and for ARMA(p, q) + GJR-GARCH(P, Q) with Normal(), StudentT(), SkewT(), and GED(), and for ARMA(p, q) + EGARCH(P, Q) with Normal(), StudentT(), SkewT(), and GED().

spec = ARMA(2, 1)                 # standalone ARMA with Normal errors
spec = ARMA(1, 1) + GARCH(1, 1)
spec = ARMA(1, 1) + EGARCH(1, 1) + StudentT()

ARX / HARX

Array-based mean models with explicit regressors via x=. Standalone ARX and HARX now ship with public fit() under the default Normal() surface, and standalone StudentT(), SkewT(), and GED() fits now ship as well. Linked ARX/HARX + GARCH(P, Q), ARX/HARX + GJRGARCH(P, Q), and ARX/HARX + EGARCH(P, Q) fits ship for Normal(), StudentT(), SkewT(), and GED().

from scivol import ARX, HARX, EGARCH, GARCH, GED, StudentT

spec = ARX(1)
result = spec.fit(y, x=x)

spec = HARX((1, 5))
result = spec.fit(y, x=x)

spec = ARX(1) + GARCH(1, 1) + StudentT()
result = spec.fit(y, x=x)

spec = HARX((1, 5)) + GARCH(1, 1) + GED()
result = spec.fit(y, x=x)

spec = ARX(1) + StudentT()
result = spec.fit(y, x=x)

spec = HARX((1, 5)) + EGARCH(1, 1) + GED()
result = spec.fit(y, x=x)

Normal()

Gaussian density. No extra parameters. Default when none is specified.

StudentT()

Heavier tails than Normal. One extra parameter: nu ($\nu > 2$). Lower $\nu$ means fatter tails; as $\nu$ grows, the distribution approaches Normal.

SkewT()

Hansen's zero-mean, unit-variance skewed Student-t. Two extra parameters: nu ($\nu > 2$) and lambda ($-1 < \lambda < 1$). $\lambda = 0$ gives a symmetric Student-t; $\lambda < 0$ shifts weight to the left tail.


Estimation

MLE

The default method.

result = spec.fit(data)

result = spec.fit(
    data,
    solver="trust",
    log_mode=True,
    verbose=True,
)

Solvers:

Solver Method Notes
"slsqp" Sequential quadratic programming Default; fast and reliable
"nelder-mead" Derivative-free simplex Slow but dependable
"trust" Trust-region (gradient + Hessian) Fast when it converges
"trust-exact" Trust-region in log-space Most stable for difficult data

Log-mode (log_mode=True) transforms constrained parameters into unconstrained space before optimization:

Parameter Constraint Transform
$\omega$ $\omega > 0$ softplus(z)
$\alpha, \beta$ $> 0$, sum $< 1$ softmax
$\gamma$ (GJR) $\gamma > 0$ 4-class softmax
$\nu$ $\nu > 2$ 2 + softplus(z)
$\lambda$ $-1 < \lambda < 1$ tanh(z)

This guarantees stationarity by construction and avoids boundary problems during optimization.

Default log_mode is benchmark-driven and may differ across model families and densities. If you do not pass log_mode, the effective path is exposed on the fitted result:

result = spec.fit(data)
result.fit_info.to_dict()
# solver='slsqp', log_mode=True/False, optimization_space='z-space'/'theta-space'

Volatility orders do not implicitly remove observations. Mean models apply the lags they require; hold_back=k requests at least k leading observations be trimmed. Results preserve pandas alignment, and the effective sample is recorded in result.fit_info.

Standalone GARCH, GJR-GARCH, and EGARCH fits accept constrained-space warm starts and explicit optimizer budgets. The same start works in theta- or z-space:

first = spec.fit(returns, maxiter=500)
updated = spec.fit(new_returns, initial_params=first, maxiter=100)
updated.fit_info.warm_started

Current default path policy:

Family Default path
GARCH + Normal / StudentT / SkewT z-space
GJRGARCH + Normal z-space
GJRGARCH + StudentT / SkewT theta-space
EGARCH + Normal theta-space
EGARCH + StudentT theta-space
EGARCH + SkewT theta-space
EGARCH + GED theta-space
ARMA + Normal / GED theta-space
ARX/HARX + Normal / StudentT / SkewT / GED theta-space
ARMA + GARCH + Normal / StudentT / SkewT / GED theta-space
ARMA + EGARCH + Normal / StudentT / SkewT / GED theta-space
ARX/HARX + GARCH + Normal / StudentT / SkewT / GED theta-space
ARX/HARX + EGARCH + Normal / StudentT / SkewT / GED theta-space

For EGARCH + Normal, the current SLSQP policy is based on targeted checks of the shipped EGARCH(1,1) and EGARCH(2,1) surfaces. Those runs showed theta-space matching z-space on fit quality while usually converging in fewer iterations and less wall time.

QMLE

Quasi-maximum likelihood: fit under Normal likelihood, then compute sandwich standard errors valid under distributional misspecification. Pass method='qmle':

spec = GARCH(1, 1) + Normal()
result = spec.fit(data, method='qmle')

result.std_errors        # MLE standard errors
result.std_errors_robust # sandwich (robust) standard errors

This works for standalone GARCH, standalone GJRGARCH, and joint ARMA + GARCH specifications. For joint fits, the robust covariance is computed for the full mean-plus-volatility parameter vector under the Normal QMLE step.

For Student-t or Skew-t, QMLE runs a two-step procedure: first it estimates the mean/volatility parameters under Normal likelihood with sandwich errors, then it fixes those parameters and estimates the distribution parameters by MLE.

from scivol import ARMA

spec = GARCH(1, 1) + StudentT()
result = spec.fit(data, method='qmle')

spec = GJRGARCH(1, 1) + Normal()
result = spec.fit(data, method='qmle')

spec = ARMA(2, 1) + GARCH(1, 2) + SkewT()
result = spec.fit(data, method='qmle')

Automatic model selection

By GARCH order

Search over lag orders with auto=True:

spec = GARCH(auto=True) + Normal()      # p, q in [1, 3]
spec = GJRGARCH(auto=True) + Normal()

spec = GARCH(auto={'max_p': 2, 'max_q': 2}) + Normal()  # narrower grid
spec = GJRGARCH(p=1, q='auto') + StudentT()              # fix p, search q

By volatility model

AutoVol searches across both GARCH and GJRGARCH families:

from scivol import AutoVol

spec = AutoVol() + Normal()
result = spec.fit(returns)

spec = AutoVol(candidates=['GJRGARCH'], max_p=2, max_q=2) + StudentT()

By distribution

AutoDensity searches across Normal, StudentT, and SkewT:

from scivol import AutoDensity

spec = GARCH(1, 1) + AutoDensity()
spec = GARCH(1, 1) + AutoDensity(candidates=['Normal', 'StudentT'])

Combine them to search volatility model, order, and distribution at once:

from scivol import AutoVol, AutoDensity

spec = AutoVol() + AutoDensity()
print(len(spec.preview_candidates()))

result = spec.fit(returns, verbose_selection=True)

print(result.spec)
result.selection_summary()
df = result.selection.to_dataframe()

Selection criterion

The default score is:

$$ \mathrm{Score} = \mathrm{AIC} + \mathrm{diagnostic_weight} \times n_{\mathrm{failed_tests}} $$

where n_failed_tests counts failures of the DGT and Ljung-Box tests. Default diagnostic_weight is 50.

Use a built-in criterion with select_by:

result = spec.fit(returns)                 # "aic+diag" default
result = spec.fit(returns, select_by="aic")
result = spec.fit(returns, select_by="bic")
result = spec.fit(returns, select_by="hqic")

Use OOS selection to fit each candidate on the training portion, score the holdout, then refit the winner on the full sample:

result = spec.fit(returns, select_by="oos")  # log score, last 20%
result = spec.fit(returns, select_by="oos", oos_test_size=500)

oos_loss="qlike" and oos_loss="mse" score variance forecasts, not the full predictive density. They are blocked with AutoDensity unless you pass allow_density_blind_loss=True.

For full control, pass a callable:

def my_criterion(result, diagnostics):
    score = result.bic
    if diagnostics is not None:
        if diagnostics['dgt']['p_value'] < 0.01:
            score += 200
        if diagnostics['ljung_box'][1]['reject']:
            score += 100
    return score

spec = AutoVol() + AutoDensity()
result = spec.fit(returns, criterion=my_criterion)

The diagnostics dict matches what result.diagnostic_tests() returns:

{
    'distribution': 'StudentT',
    'dist_params': {'nu': 7.42, 'lam': None},
    'n_obs': 1000,
    'alpha': 0.05,
    'dgt': {
        'n_cells': 40, 'chi2_stat': 34.2,
        'df': 39, 'p_value': 0.689, 'reject': False,
    },
    'ljung_box': {
        1: {'lags': 10, 'q_stat': 8.42, 'p_value': 0.588, 'reject': False},
        2: {'lags': 10, 'q_stat': 7.91, 'p_value': 0.637, 'reject': False},
        3: {'lags': 10, 'q_stat': 11.2, 'p_value': 0.340, 'reject': False},
        4: {'lags': 10, 'q_stat': 9.87, 'p_value': 0.452, 'reject': False},
    },
    'pit': np.ndarray,
}

Pass diagnostic_kwargs to tune test settings:

result = spec.fit(returns, diagnostic_kwargs={'lags': 20, 'n_cells': 50})

When criterion is provided, diagnostic_weight is ignored.

Parallel fitting

Auto-selection fits candidates in parallel by default:

result = spec.fit(returns)           # all cores
result = spec.fit(returns, n_jobs=4) # 4 workers
result = spec.fit(returns, n_jobs=1) # sequential

Multi-series fitting

Fit one specification to many series at once:

import pandas as pd

spec = GARCH(1, 1) + Normal()
returns = pd.DataFrame(
    {"asset_1": returns1, "asset_2": returns2, "asset_3": returns3}
)
results = spec.fit(returns, n_jobs=4)

for name, r in results.items():
    print(f"{name}: persistence = {r.garch_params.persistence:.4f}")

For repeated portfolio work, fit_many() returns structured successes and failures and supports per-fit limits and prior results:

batch = spec.fit_many(
    returns,
    n_jobs=4,
    maxiter=500,
    timeout=10.0,
    on_error="record",
)

updated = spec.fit_many(new_returns, initial_results=batch, n_jobs=4)
batch.results
batch.failures
batch.warning_summary
batch.to_dataframe()

Rolling windows reuse each series' last successful parameters:

rolling = spec.fit_rolling(returns, window=1000, step=20, n_jobs=4)
latest = rolling[-1].batch

Auto-selection works here too -- each series gets its own best model:

spec = GARCH(auto=True) + AutoDensity()
results = spec.fit(returns, n_jobs=4)

Inspecting candidates

result.selection_summary()

for c in result.selection.candidates[:5]:
    print(f"{c.spec}: AIC={c.aic:.2f}, BIC={c.bic:.2f}, Score={c.score:.2f}")

result.selection.best
result.selection.to_dataframe()

QMLE with AutoDensity is redundant (QMLE always uses Normal likelihood). scivol warns and fits Normal only.


Results

spec.fit() returns an EstimationResult.

Parameters

result.params            # flat array: [omega, alpha_1, ..., beta_q, nu?, lambda?]

gp = result.garch_params
gp.omega                 # constant
gp.alpha                 # ARCH coefficients (array)
gp.gamma                 # leverage coefficients (GJR-GARCH only)
gp.beta                  # GARCH coefficients (array)
gp.persistence           # alpha+beta (GARCH) or alpha+0.5*gamma+beta (GJR-GARCH)

dp = result.dist_params
dp.nu                    # degrees of freedom (StudentT, SkewT)
dp.lam                   # skewness (SkewT)

Fit statistics

result.loglikelihood
result.aic
result.bic
result.hqic

Conditional variances and residuals

result.sigma2        # sigma^2_t
result.volatility    # sigma_t
result.std_resid     # epsilon_t / sigma_t

Standard errors

result.std_errors        # MLE (from inverse Hessian)
result.std_errors_robust # sandwich (QMLE only)
result.cov_matrix        # H⁻¹
result.cov_robust        # H⁻¹ @ OPG @ H⁻¹
result.warnings          # persistent fit warnings shown by result.summary()

Output

result.fit_info             # effective solver / theta-vs-z path
result.summary()              # full table
result.summary(robust=True)   # with sandwich SEs
print(result)                 # compact
result.to_dict()              # for programmatic use

Display settings

Override how parameter names appear in summaries, print output, and to_dict() keys:

import scivol

scivol.settings.names.gamma = "leverage"
scivol.settings.names.nu = "df"
scivol.settings.names.alpha = "a"

# Now result.summary() shows "leverage[1]" instead of "gamma[1]"
# and result.to_dict() uses "leverage" as a key

Overrides apply to indexed variants too: renaming alpha to a turns alpha[1], alpha[2] into a[1], a[2].

Reset with:

scivol.settings.names.reset()

Available names: omega, alpha, gamma, beta, nu, lambda, const, ar, ma.

Internal attribute names (gp.omega, dp.nu, etc.) never change.


Diagnostics

DGT and Ljung-Box tests

Check whether the fitted distribution captures the data:

spec = GARCH(1, 1) + StudentT()
result = spec.fit(returns)
result.diagnostic_tests()
======================================================================
                     Model Diagnostic Tests
======================================================================
Distribution:  StudentT (nu=7.42)
Observations:  1000
Alpha:         0.05

DGT Test (Diebold-Gunther-Tay)
----------------------------------------------------------------------
  Cells:       40         df:          39
  Chi2 stat:   34.20      p-value:     0.6891
  Reject H0:   No (uniform PIT)

Ljung-Box Tests on PIT Moments
----------------------------------------------------------------------
  Moment       Lags       Q-stat      p-value     Reject
  ---------- ------ ------------ ------------ ----------
  (u-0.5)^1     10         8.42       0.5880         No
  (u-0.5)^2     10         7.91       0.6371         No
  (u-0.5)^3     10        11.23       0.3396         No
  (u-0.5)^4     10         9.87       0.4518         No
======================================================================

The DGT test checks whether PIT residuals are uniform -- "No" rejection means the distribution fits. Ljung-Box tests check for serial correlation in PIT moments: moment 1 targets the mean, moment 2 the variance, moments 3--4 skewness and kurtosis.

result.diagnostic_tests(alpha=0.01, n_cells=50, lags=20)

diag = result.diagnostic_tests(print_results=False)
diag['dgt']['p_value']
diag['ljung_box'][2]['reject']

Auto-selection uses these tests internally to penalize poorly fitting candidates.

Every shipped univariate surface has full theta-space and log-space validation against independent AD reference implementations. The machine-checked contract and current verification status live in MODELS.md.


API reference

Exports

from scivol import (
    EGARCH, GARCH, GJRGARCH, ARMA, DCC,
    Normal, StudentT, SkewT,
    AutoDensity, AutoVol,
    BatchFitResult, RollingFitResult,
    Component, CompositeSpec, Role,
    DCCParams, DCCResult, MultivariateScenarioResult,
    settings, __version__,
)

spec.fit()

result = spec.fit(
    data,                      # 1D array, Series, or DataFrame
    hold_back=0,               # explicit leading observations to trim
    scale=None,                # optional estimation scale
    common_sample=None,        # align auto-selection candidates
    method="mle",              # "mle" or "qmle"
    solver="trust",
    log_mode=True,
    initial_params=None,        # prior result or constrained parameter vector
    maxiter=5000,               # standalone volatility fits
    maxfev=50000,               # derivative-free evaluation budget
    verbose=False,
    n_jobs=None,               # parallel workers (selection or multi-series)
    diagnostic_weight=50.0,    # AIC penalty per failed test
    select_by="aic+diag",      # "aic+diag", "aic", "bic", "hqic", or "oos"
    criterion=None,            # custom scoring callable
    diagnostic_kwargs=None,    # forwarded to diagnostic_tests()
    oos_loss="logscore",       # for select_by="oos"
    oos_test_size=0.2,         # float fraction or int holdout count
)

GARCH

g = GARCH(1, 1)
g = GARCH(auto=True)
g = GARCH(auto={'max_p': 2, 'max_q': 2})

g.p, g.q, g.n_params, g.signature
g.fitted_params   # {'omega': ..., 'alpha': [...], 'beta': [...]}
g.persistence()
g.is_stationary()
g.unconditional_variance()

GJRGARCH

gjr = GJRGARCH(1, 1)
gjr = GJRGARCH(auto=True)

gjr.n_params              # 1 + 2p + q
gjr.fitted_params         # includes 'gamma'
gjr.persistence()         # alpha + 0.5*gamma + beta
gjr.persistence(p_neg=0.6)

AutoVol

av = AutoVol()
av = AutoVol(candidates=['GJRGARCH'], max_p=2, max_q=2)
av.get_candidates()  # list of (model, p, q) tuples

AutoDensity

ad = AutoDensity()
ad = AutoDensity(candidates=['Normal', 'StudentT'])

DCC

dcc = DCC(1, 1)
dcc.p, dcc.q, dcc.n_params, dcc.signature

t_dcc = DCC(1, 1, distribution="studentt")

EstimationResult

result.params, result.garch_params, result.dist_params
result.loglikelihood, result.aic, result.bic, result.hqic
result.sigma2, result.volatility, result.std_resid
result.std_errors, result.std_errors_robust
result.cov_matrix, result.cov_robust
result.success, result.niter, result.time_elapsed
result.summary(), result.to_dict(), result.diagnostic_tests()
result.selection_summary()
result.selection.candidates   # list of all evaluated models
result.selection.to_dataframe()

DCCResult

result.params, result.log_likelihood, result.aic, result.bic
result.params.nu              # Student-t DCC
result.Rt
result.corr(0, 1)
result.unconditional_corr
result.std_errors, result.std_errors_robust
result.scenarios(10_000, seed=7)
result.simulate(250, seed=7)
result.summary()

Examples

Fit GARCH(1,1) and inspect results

import numpy as np
from scivol import GARCH, Normal

returns = np.random.randn(1000) * 0.01

spec = GARCH(1, 1) + Normal()
result = spec.fit(returns)

print(f"Persistence: {result.garch_params.persistence:.4f}")
print(f"Log-likelihood: {result.loglikelihood:.2f}")
result.summary()

Compare MLE and sandwich standard errors

from scivol import GARCH, Normal

spec = GARCH(1, 1) + Normal()
result = spec.fit(returns, method='qmle')

print("Parameter       MLE SE    Robust SE")
print("-" * 40)
for i, name in enumerate(['omega', 'alpha', 'beta']):
    print(f"{name:10}  {result.std_errors[i]:10.6f}  {result.std_errors_robust[i]:10.6f}")

GJR-GARCH leverage effect

from scivol import GJRGARCH, StudentT

spec = GJRGARCH(1, 1) + StudentT()
result = spec.fit(returns)

gp = result.garch_params
print(f"alpha: {gp.alpha[0]:.4f}")
print(f"gamma: {gp.gamma[0]:.4f}")
print(f"beta:  {gp.beta[0]:.4f}")
print(f"nu:    {result.dist_params.nu:.2f}")

if gp.gamma[0] > 0:
    ratio = (gp.alpha[0] + gp.gamma[0]) / gp.alpha[0]
    print(f"Negative shocks hit {ratio:.1f}x harder than positive")
from scivol import AutoVol, AutoDensity

spec = AutoVol() + AutoDensity()
result = spec.fit(returns, verbose_selection=True)

print(f"Best: {result.spec}")
result.selection_summary()

Fit DCC and inspect correlations

import numpy as np
import pandas as pd
from scivol import DCC, GARCH, Normal

rng = np.random.default_rng(42)
returns = rng.standard_normal(1000) * 0.01
returns_df = pd.DataFrame(
    {
        "stock": returns,
        "bond": returns * 0.3 + rng.standard_normal(len(returns)) * 0.005,
        "commodity": returns * -0.2 + rng.standard_normal(len(returns)) * 0.008,
    }
)

dcc = DCC(1, 1)
result = dcc.fit(returns_df, univariate_spec=GARCH(1, 1) + Normal())

stock_bond = result.corr("stock", "bond")
print(stock_bond.tail())
print(result.unconditional_corr)

One-step-ahead variance forecast

from scivol import GARCH, GJRGARCH, Normal
import numpy as np

# GARCH(1,1)
spec = GARCH(1, 1) + Normal()
result = spec.fit(returns)

gp = result.garch_params
h_next = gp.omega + gp.alpha[0] * returns[-1]**2 + gp.beta[0] * result.sigma2[-1]
print(f"GARCH forecast sigma: {np.sqrt(h_next):.6f}")

# GJR-GARCH(1,1)
spec_gjr = GJRGARCH(1, 1) + Normal()
result_gjr = spec_gjr.fit(returns)

gp = result_gjr.garch_params
indicator = 1.0 if returns[-1] < 0 else 0.0
h_next = (gp.omega
    + gp.alpha[0] * returns[-1]**2
    + gp.gamma[0] * indicator * returns[-1]**2
    + gp.beta[0] * result_gjr.sigma2[-1])
print(f"GJR forecast sigma:   {np.sqrt(h_next):.6f}")

Metadata

Release files for scivol 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for scivol 0.3.0
File Size Uploaded Provenance Actions
scivol-0.3.0.tar.gz 280.4 kB With provenance Details for scivol-0.3.0.tar.gz

Built distributions (wheels)

Table of built distributions (wheels) for scivol 0.3.0
File Actions
scivol-0.3.0-cp312-cp312-win_amd64.whl CPython 3.12 CPython 3.12 Windows x86-64 Details for scivol-0.3.0-cp312-cp312-win_amd64.whl
scivol-0.3.0-cp312-cp312-musllinux_1_2_x86_64.whl CPython 3.12 CPython 3.12 Linux musl 1.2+ x86-64 Details for scivol-0.3.0-cp312-cp312-musllinux_1_2_x86_64.whl
scivol-0.3.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl CPython 3.12 CPython 3.12 Linux glibc 2.24+ x86-64, Linux glibc 2.28+ x86-64 Details for scivol-0.3.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
scivol-0.3.0-cp312-cp312-macosx_11_0_arm64.whl CPython 3.12 CPython 3.12 macOS 11.0+ ARM64 Details for scivol-0.3.0-cp312-cp312-macosx_11_0_arm64.whl
scivol-0.3.0-cp311-cp311-win_amd64.whl CPython 3.11 CPython 3.11 Windows x86-64 Details for scivol-0.3.0-cp311-cp311-win_amd64.whl
scivol-0.3.0-cp311-cp311-musllinux_1_2_x86_64.whl CPython 3.11 CPython 3.11 Linux musl 1.2+ x86-64 Details for scivol-0.3.0-cp311-cp311-musllinux_1_2_x86_64.whl
scivol-0.3.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl CPython 3.11 CPython 3.11 Linux glibc 2.24+ x86-64, Linux glibc 2.28+ x86-64 Details for scivol-0.3.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
scivol-0.3.0-cp311-cp311-macosx_11_0_arm64.whl CPython 3.11 CPython 3.11 macOS 11.0+ ARM64 Details for scivol-0.3.0-cp311-cp311-macosx_11_0_arm64.whl
scivol-0.3.0-cp310-cp310-win_amd64.whl CPython 3.10 CPython 3.10 Windows x86-64 Details for scivol-0.3.0-cp310-cp310-win_amd64.whl
scivol-0.3.0-cp310-cp310-musllinux_1_2_x86_64.whl CPython 3.10 CPython 3.10 Linux musl 1.2+ x86-64 Details for scivol-0.3.0-cp310-cp310-musllinux_1_2_x86_64.whl
scivol-0.3.0-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl CPython 3.10 CPython 3.10 Linux glibc 2.24+ x86-64, Linux glibc 2.28+ x86-64 Details for scivol-0.3.0-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
scivol-0.3.0-cp310-cp310-macosx_11_0_arm64.whl CPython 3.10 CPython 3.10 macOS 11.0+ ARM64 Details for scivol-0.3.0-cp310-cp310-macosx_11_0_arm64.whl

Total release size: 23.2 MB

Release files / scivol-0.3.0.tar.gz

Download URL scivol-0.3.0.tar.gz
Size 280.4 kB
Tags Source
SHA-256 checksum
How to use checksums
d9387cdc4c6eca87759eb36a869123b8519b6ca348543689ccf11dcb07a71551
BLAKE2b-256 checksum
How to use checksums
fa36143ad94ddd9b1902aaff0d9d86bc913689e11eb024a96eb0669d524990ed
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 24, 2026.

Transparency log

Release files / scivol-0.3.0-cp312-cp312-win_amd64.whl

Download URL scivol-0.3.0-cp312-cp312-win_amd64.whl
Size 719.7 kB
Tags CPython 3.12 Windows x86-64
SHA-256 checksum
How to use checksums
34a0ac5601179ef1caadfe341205f7810f8b264f3f8852b0225b100feccc9bf5
BLAKE2b-256 checksum
How to use checksums
a5cab19792d35bf86a3729b6b1e46b3f75bf1dc6f5bba4b99b94bcfde5637725
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 24, 2026.

Transparency log

Release files / scivol-0.3.0-cp312-cp312-musllinux_1_2_x86_64.whl

Download URL scivol-0.3.0-cp312-cp312-musllinux_1_2_x86_64.whl
Size 3.1 MB
Tags CPython 3.12 Linux musl 1.2+ x86-64
SHA-256 checksum
How to use checksums
766a1a2ab445c2d78fe3b2c103edeed06dc3384de851b832107d4422d711e690
BLAKE2b-256 checksum
How to use checksums
3be2493b36da18b2dbaa7fd7cb0ed7be7b65c2ee6172af323e9426f2361df78a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 24, 2026.

Transparency log

Release files / scivol-0.3.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl

Download URL scivol-0.3.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
Size 3.0 MB
Tags CPython 3.12 Linux glibc 2.24+ x86-64 Linux glibc 2.28+ x86-64
SHA-256 checksum
How to use checksums
e3052551f0a6e850e3a3da53918114158420f5737a2ab6caf4e060f6b86e6630
BLAKE2b-256 checksum
How to use checksums
11c705f30af87ff5d2038f22d5dcadde97333d4a8d8ba1df43c0fc8934bfdfa5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 24, 2026.

Transparency log

Release files / scivol-0.3.0-cp312-cp312-macosx_11_0_arm64.whl

Download URL scivol-0.3.0-cp312-cp312-macosx_11_0_arm64.whl
Size 792.8 kB
Tags CPython 3.12 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
371164c866184af7e7874f728b44004c38e5b3009eac41a165124d4f93c67dba
BLAKE2b-256 checksum
How to use checksums
6a117968699378267828c37735b2a7a962e5a56d2c9f060a02dc3af5d6c33636
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 24, 2026.

Transparency log

Release files / scivol-0.3.0-cp311-cp311-win_amd64.whl

Download URL scivol-0.3.0-cp311-cp311-win_amd64.whl
Size 719.7 kB
Tags CPython 3.11 Windows x86-64
SHA-256 checksum
How to use checksums
c02e6e931b48dc0f93ee4690e1005fb5d0d2a6e42aaf1205487dab90be356cba
BLAKE2b-256 checksum
How to use checksums
e17ec4159bb0bc8cb38d43d0e2bca356891688f2463df0bf5fa99b80f5d1b373
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 24, 2026.

Transparency log

Release files / scivol-0.3.0-cp311-cp311-musllinux_1_2_x86_64.whl

Download URL scivol-0.3.0-cp311-cp311-musllinux_1_2_x86_64.whl
Size 3.1 MB
Tags CPython 3.11 Linux musl 1.2+ x86-64
SHA-256 checksum
How to use checksums
71cea391f3415d7fb501e07231b2cdb5e256ae98c72e2915884730a268fce247
BLAKE2b-256 checksum
How to use checksums
4d44f71c74b77a5e67c7ef3d5145d4909042be830a8b32f42f4d050d011c232a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 24, 2026.

Transparency log

Release files / scivol-0.3.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl

Download URL scivol-0.3.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
Size 3.0 MB
Tags CPython 3.11 Linux glibc 2.24+ x86-64 Linux glibc 2.28+ x86-64
SHA-256 checksum
How to use checksums
fdd6e42b315f67ce1f787aa2d6892f1789fde235b048fb4080a16ffd9a40d827
BLAKE2b-256 checksum
How to use checksums
ad8e8fec8d1a9313e2106b00c258e1526a0d0c9e4a3ce00e5b132f2c4bd3d490
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 24, 2026.

Transparency log

Release files / scivol-0.3.0-cp311-cp311-macosx_11_0_arm64.whl

Download URL scivol-0.3.0-cp311-cp311-macosx_11_0_arm64.whl
Size 792.9 kB
Tags CPython 3.11 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
3dece8fb652f2caa0f7a60ef435e5b263a409a9292b203cad27513fc2459c22b
BLAKE2b-256 checksum
How to use checksums
ec5adc677b8c1a8725eb1a8c46d57370e04ebaef5b60432f58f25617f10ff0ac
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 24, 2026.

Transparency log

Release files / scivol-0.3.0-cp310-cp310-win_amd64.whl

Download URL scivol-0.3.0-cp310-cp310-win_amd64.whl
Size 719.7 kB
Tags CPython 3.10 Windows x86-64
SHA-256 checksum
How to use checksums
c87fbe31e39f7f6d92b40b8bb739d0a3ab02aa1546d68cb7169b056effbbe655
BLAKE2b-256 checksum
How to use checksums
a5f72068471795117cdf5264bb08b072ef98a0d9c8d7d5a589831b658b8063fc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 24, 2026.

Transparency log

Release files / scivol-0.3.0-cp310-cp310-musllinux_1_2_x86_64.whl

Download URL scivol-0.3.0-cp310-cp310-musllinux_1_2_x86_64.whl
Size 3.1 MB
Tags CPython 3.10 Linux musl 1.2+ x86-64
SHA-256 checksum
How to use checksums
2a411911931e1ff8efdda52caf16ed2d81ea33561610c6439597f9cb15a028c5
BLAKE2b-256 checksum
How to use checksums
cffbb4b5acc44ad61ff2da1284ab6b58bfa61801cb5ae5b5b05f7ca21cf7c6fe
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 24, 2026.

Transparency log

Release files / scivol-0.3.0-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl

Download URL scivol-0.3.0-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
Size 3.0 MB
Tags CPython 3.10 Linux glibc 2.24+ x86-64 Linux glibc 2.28+ x86-64
SHA-256 checksum
How to use checksums
6b19ed30fb7a40b6ca7fd4f8c320184cdd353dc090c43c46cb621efaf44c0ea8
BLAKE2b-256 checksum
How to use checksums
a1c55bf0c1ec40cd7316cdd26247bc7332592cef982210e24879c48771b93ba5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 24, 2026.

Transparency log

Release files / scivol-0.3.0-cp310-cp310-macosx_11_0_arm64.whl

Download URL scivol-0.3.0-cp310-cp310-macosx_11_0_arm64.whl
Size 792.9 kB
Tags CPython 3.10 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
fd6dac1a3926cf0dfbf8d8174d16959dfc284112bbf28632e649f10a4e3b9ccb
BLAKE2b-256 checksum
How to use checksums
167f7f3108148281189799d4bee43d808a96a12c8d64c8741c976d4b15d6465d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 24, 2026.

Transparency log

Security for scivol 0.3.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 13 files in this release have been uploaded with provenance metadata.

Select from 13 files

Attestation for scivol-0.3.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 24, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page