jump-models

repository·master·Indexed 41 days ago

https://github.com/yizhan-oliver-shu/jump-models

A Python library for statistical jump models (JMs) used for unsupervised regime identification in time series data. It provides scikit-learn-style APIs for discrete, continuous (CJM), and sparse jump models (SJM), featuring both online and offline inference to prevent look-ahead bias in real-time applications. Includes preprocessing tools like StandardScalerPD and DataClipperStd, as well as utilities for state sorting and regime visualization.

Tokens
8.5K
Snippets
31
Records
38
Agent score
50%

What's inside jump-models

  1. Install jumpmodels

    master

    Install the core library using pip:

    pip install jumpmodels

    To run the provided example notebooks (which require yfinance and jupyterlab), install the package with the [example] extra:

    pip install jumpmodels[example]

    Dependencies:

    • Python (>=3.8)
    • numpy, pandas, scipy, scikit-learn, matplotlib

    Note on Plotting: The plotting functions in plot.py work best with a local LaTeX installation (e.g., MiKTeX). If you do not wish to install LaTeX, you can comment out the matplotlib_setting() function at the beginning of plot.py to avoid issues.

  2. How Sparse Jump Model optimization works

    master

    The SparseJumpModel uses a coordinate descent-like approach to alternate between two steps:

    1. Fit Jump Model: Fix the feature weights $w$, and fit the underlying JumpModel on the weighted data. The weights used are $\sqrt{w}$.
    2. Optimize Weights: Fix the cluster assignments (probabilities), and update the weights $w$ by solving a Lasso problem. The objective is to maximize the Between Cluster Sum of Squares (BCSS) subject to an L1-norm constraint on the weights.

    This loop continues until the weights converge (change is less than tol_w) or the maximum number of iterations (max_iter) is reached.

  3. Perform Online vs. Offline Inference

    master

    After fitting a model, you can use it to predict regimes on new data (test sets).

    Online Inference (predict_online)

    Recommended for real-time/trading applications. This method ensures that the regime inference for period $t$ is based solely on data available up to the end of that period, preventing look-ahead bias.

    Offline Inference (predict)

    This method performs state decoding using the entire dataset at once. While it may produce smoother or more accurate results in hindsight, it is not realistic for real-time applications because it uses future data to inform current state assignments.

    # Online inference (no look-ahead bias)
    labels_test_online = jm.predict_online(X_test_processed)
    
    # Probabilistic online inference (for CJM)
    proba_test_online = cjm.predict_proba_online(X_test_processed)
    
    # Offline inference (uses full dataset)
    labels_test = jm.predict(X_test_processed)
  4. Extend BaseDataClipper to create custom clippers

    master

    If you need to implement custom clipping logic (e.g., clipping based on percentiles rather than standard deviation), you can inherit from BaseDataClipper.

    To create a functional subclass, you must implement the fit(self, X: DF_ARR_TYPE) method. The base class already provides implementations for:

    • fit_transform(X): Fits the clipper and transforms the data in one step.
    • transform(X): Clips the input data using the self.lb (lower bound) and self.ub (upper bound) attributes, which must be set as NumPy arrays during the fit step.
  5. How discrete and continuous jump models differ

    master

    The JumpModel supports two mathematical formulations controlled by the cont parameter:

    Discrete Jump Model (cont=False)

    • The state space is a set of discrete indices {0, 1, ..., n_c - 1}.
    • The jump_penalty is converted into a penalty matrix where off-diagonal elements are the scalar penalty and diagonal elements are zero.

    Continuous Jump Model (cont=True)

    • The state space is a discretized probability simplex.
    • The jump_penalty is applied to the pairwise $L_1$ distance between probability vectors.
    • Uses grid_size to determine the density of the simplex discretization.
    • Supports mode_loss to ensure the penalty matrix corresponds to a valid Transition Probability Matrix (TPM).
  6. Use the scikit-learn style API for Jump Models

    master

    The JumpModel and SparseJumpModel classes implement standard methods for model lifecycle management:

    • .fit(): Fits the model to the provided time series data.
    • .predict(): Predicts the current state.
    • .predict_proba(): Predicts the probability of being in each state.
    • .set_params(): Resets or updates model parameters.

    For time series applications requiring real-time or sequential processing, the library also provides online prediction methods:

    • .predict_online()
    • .predict_proba_online()
  7. Import JumpModel and SparseJumpModel

    master

    The library provides two primary classes for regime identification in time series data:

    • JumpModel: Implements the original discrete Jump Model (JM) and the Continuous Jump Model (CJM).
    • SparseJumpModel: Implements the Sparse Jump Model (SJM) with feature selection.

    Both classes follow a scikit-learn-style API and support pandas DataFrames for input and output.

    from jumpmodels.jump import JumpModel                 # JM & CJM class
    from jumpmodels.sparse_jump import SparseJumpModel    # Sparse JM class
  8. Solve state assignment with `dp`

    master

    The dp function solves the optimization problem: $$\min \sum_{t=0}^{T-1} L(t, s_t) + \sum_{t=1}^{T-1} \Lambda(s_{t-1}, s_t)$$ using a dynamic programming algorithm.

    Parameters:

    • loss_mx: Loss matrix $(n_s, n_c)$. NaN values are replaced with inf to make those states unreachable.
    • penalty_mx: Jump penalty matrix $(n_c, n_c)$.
    • return_value_mx: If True, returns the value matrix instead of the optimal path.

    Returns:

    • If return_value_mx=False: (optimal_state_assignments, optimal_loss_value).
    • If return_value_mx=True: value_matrix.
  9. Perform the E-step with `do_E_step`

    master

    The do_E_step function computes the loss matrix and solves the state assignment problem using a dynamic programming (DP) algorithm.

    It can handle both hard clustering and continuous models. If prob_vecs is provided, it adjusts the loss matrix for the continuous model.

    Parameters:

    • X: Input data $(n_s, n_f)$.
    • centers_: Cluster centers $(n_c, n_f)$. Can contain NaN values.
    • penalty_mx: Transition cost matrix $(n_c, n_c)$.
    • prob_vecs: (Optional) Probability vectors for the continuous model.
    • return_value_mx: If True, returns the DP value matrix (useful for online inference).
  10. Sort states by financial criteria

    master

    The sort_param_dict function reorders the states (clusters) within a parameter dictionary based on specific criteria. This is primarily used to ensure consistent state ordering (e.g., high return states first).

    Supported sort_by values:

    • "cumret": Sorts by decreasing cumulative return (-ret_ * freq).
    • "vol": Sorts by increasing volatility (vol_).
    • "freq": Sorts by decreasing frequency (derived from proba_).
    • "ret": Sorts by decreasing return (-ret_).

    Note: nan values are ideally sorted to the end.

    from jumpmodels.base import sort_param_dict
    
    # Example parameter dictionary
    params = {
        'ret_': np.array([0.01, 0.05]),
        'vol_': np.array([0.1, 0.2]),
        'proba_': np.array([[0.5, 0.5], [0.5, 0.5]])
    }
    
    sort_param_dict(params, sort_by='ret')
  11. Preprocess data with StandardScalerPD and DataClipperStd

    master

    The jumpmodels.preprocess module provides tools to prepare feature data. It is recommended to fit these on training data and then transform the test data to avoid data leakage.

    • DataClipperStd(mul=...): Clips data within a specified number of standard deviations.
    • StandardScalerPD(): Performs standardization.

    Both classes support pandas DataFrames and numpy arrays, but using DataFrames is preferred to preserve date indices and column labels.

    from jumpmodels.preprocess import StandardScalerPD, DataClipperStd
    
    clipper = DataClipperStd(mul=3.)
    scalar = StandardScalerPD()
    
    # Fit and transform training data
    X_train_processed = scalar.fit_transform(clipper.fit_transform(X_train))
    
    # Transform test data using parameters from training
    X_test_processed = scalar.transform(clipper.transform(X_test))
  12. Validate input for prediction in BaseClusteringAlgo

    master

    When implementing a .predict method in a subclass of BaseClusteringAlgo, use check_X_predict_func to ensure the input data X is a 2D array and its feature dimension matches the dimension of the fitted centers_.

    # Inside a subclass of BaseClusteringAlgo
    def predict(self, X):
        X_validated = self.check_X_predict_func(X)
        # proceed with prediction logic using X_validated