Skip to content

Types & registry

Core types

types

Shared data structures for jaxfolio.

These are lightweight, framework-agnostic containers. Optimizers return a :class:PortfolioResult; the backtester and plotting utilities consume it.

OptimizerConfig dataclass

OptimizerConfig(
    risk_free_rate: float = 0.0,
    long_only: bool = True,
    weight_bounds: tuple[float, float] | None = None,
    max_iter: int = 2000,
    solver: str = "spg",
    learning_rate: float | None = None,
    tol: float = 1e-07,
    l2_reg: float = 0.0,
)

Configuration shared by the projected-gradient classical optimizers.

Attributes:

Name Type Description
risk_free_rate float

Per-period risk-free rate used by Sharpe-style objectives.

long_only bool

If True, weights are projected onto the simplex (no shorting).

weight_bounds tuple[float, float] | None

Explicit (lower, upper) per-asset bounds applied alongside the budget constraint. When left as None the effective bounds follow long_only: (0, 1) when long-only, (-1, 1) when shorting is allowed — so OptimizerConfig(long_only=False) permits shorting without also having to set bounds. Set this to override those defaults.

max_iter int

Maximum projected-gradient iterations.

solver str

Which projected-gradient solver to use. "spg" (default) is a spectral projected-gradient method with Barzilai-Borwein step sizes: it needs no learning-rate tuning and converges to the constrained optimum (matching a dedicated QP solver) in far fewer iterations. "adam" keeps the smooth optax-Adam dynamics, which are preferable when differentiating through the optimizer to train an allocation policy.

learning_rate float | None

Step size for the solver. None (default) lets the solver pick it automatically — a curvature-based (1/L) initial step for "spg", or 1e-2 for "adam". Set a float to override.

tol float

Convergence tolerance. For "spg" this is the projected-gradient (KKT stationarity) norm; for "adam" it is the weight-update norm.

l2_reg float

Optional L2 penalty on weights (encourages diversification).

bounds
bounds() -> tuple[float, float]

Resolve the effective per-asset weight bounds.

Honors an explicit weight_bounds; otherwise defaults to (0, 1) for a long-only portfolio and (-1, 1) when shorting is allowed.

Source code in src/jaxfolio/types.py
def bounds(self) -> tuple[float, float]:
    """Resolve the effective per-asset weight bounds.

    Honors an explicit ``weight_bounds``; otherwise defaults to ``(0, 1)``
    for a long-only portfolio and ``(-1, 1)`` when shorting is allowed.
    """
    if self.weight_bounds is not None:
        return self.weight_bounds
    return (0.0, 1.0) if self.long_only else (-1.0, 1.0)

PortfolioResult dataclass

PortfolioResult(
    weights: ndarray,
    assets: list[str],
    method: str,
    expected_return: float | None = None,
    volatility: float | None = None,
    sharpe: float | None = None,
    metadata: dict[str, Any] = dict(),
)

The output of an optimizer: weights plus diagnostics.

Attributes:

Name Type Description
weights ndarray

Portfolio weights as a 1-D numpy array (sums to 1).

assets list[str]

Asset names aligned with weights.

method str

Human-readable optimizer name.

expected_return float | None

Annualized (or per-period, if unscaled) expected portfolio return.

volatility float | None

Portfolio volatility on the same scale as expected_return.

sharpe float | None

Sharpe ratio implied by expected_return / volatility.

metadata dict[str, Any]

Free-form diagnostics (iterations, converged flag, cluster labels, ...).

as_dict
as_dict() -> dict[str, float]

Return {asset: weight} sorted by descending absolute weight.

Source code in src/jaxfolio/types.py
def as_dict(self) -> dict[str, float]:
    """Return ``{asset: weight}`` sorted by descending absolute weight."""
    pairs = zip(self.assets, self.weights.tolist(), strict=True)
    return dict(sorted(pairs, key=lambda kv: abs(kv[1]), reverse=True))
top
top(n: int = 10) -> dict[str, float]

Return the n largest holdings by absolute weight.

Source code in src/jaxfolio/types.py
def top(self, n: int = 10) -> dict[str, float]:
    """Return the ``n`` largest holdings by absolute weight."""
    return dict(list(self.as_dict().items())[:n])

Registry

registry

A lightweight registry for portfolio strategies.

Any callable with the optimizer shape method(returns, ...) -> PortfolioResult can be registered by name and then looked up, listed, and mixed with the built-ins in the backtester. Built-in optimizers register themselves at import time (see :func:_register_builtins), so :func:list_strategies always reflects the full catalog — including the user's own additions.

Usage

from jaxfolio import register_strategy, get_strategy, list_strategies @register_strategy("my_momentum", description="12-1 momentum tilt") ... def my_momentum(returns): ... ... get_strategy("my_momentum") is my_momentum True "my_momentum" in list_strategies() True

StrategyInfo dataclass

StrategyInfo(
    name: str,
    func: Strategy,
    description: str = "",
    builtin: bool = False,
)

A registered strategy: its callable, canonical name, and description.

register_strategy

register_strategy(
    name: str | None = None,
    *,
    description: str = "",
    builtin: bool = False,
    overwrite: bool = False,
) -> Callable[[Strategy], Strategy]

Register a strategy under name (defaults to the function's name).

Usable as a decorator (@register_strategy("x")) or directly (register_strategy("x")(fn)). The wrapped function is returned unchanged, so decorating never alters behavior. Re-registering an existing name raises unless overwrite=True.

Source code in src/jaxfolio/registry.py
def register_strategy(
    name: str | None = None,
    *,
    description: str = "",
    builtin: bool = False,
    overwrite: bool = False,
) -> Callable[[Strategy], Strategy]:
    """Register a strategy under ``name`` (defaults to the function's name).

    Usable as a decorator (``@register_strategy("x")``) or directly
    (``register_strategy("x")(fn)``). The wrapped function is returned unchanged,
    so decorating never alters behavior. Re-registering an existing name raises
    unless ``overwrite=True``.
    """

    def decorator(func: Strategy) -> Strategy:
        key = name or getattr(func, "__name__", None)
        if not key:
            raise ValueError("register_strategy needs a name (no __name__ on the callable)")
        if key in _REGISTRY and not overwrite:
            raise ValueError(
                f"strategy {key!r} is already registered; pass overwrite=True to replace it"
            )
        _REGISTRY[key] = StrategyInfo(name=key, func=func, description=description, builtin=builtin)
        return func

    return decorator

get_strategy

get_strategy(name: str) -> Strategy

Return the registered strategy callable for name.

Source code in src/jaxfolio/registry.py
def get_strategy(name: str) -> Strategy:
    """Return the registered strategy callable for ``name``."""
    try:
        return _REGISTRY[name].func
    except KeyError:
        raise KeyError(f"unknown strategy {name!r}; registered: {sorted(_REGISTRY)}") from None

strategy_info

strategy_info(name: str) -> StrategyInfo

Return the full :class:StrategyInfo record for name.

Source code in src/jaxfolio/registry.py
def strategy_info(name: str) -> StrategyInfo:
    """Return the full :class:`StrategyInfo` record for ``name``."""
    try:
        return _REGISTRY[name]
    except KeyError:
        raise KeyError(f"unknown strategy {name!r}") from None

list_strategies

list_strategies(
    *, builtin_only: bool = False, custom_only: bool = False
) -> list[str]

List registered strategy names (optionally filtered by origin).

Source code in src/jaxfolio/registry.py
def list_strategies(*, builtin_only: bool = False, custom_only: bool = False) -> list[str]:
    """List registered strategy names (optionally filtered by origin)."""
    names = sorted(_REGISTRY)
    if builtin_only:
        return [n for n in names if _REGISTRY[n].builtin]
    if custom_only:
        return [n for n in names if not _REGISTRY[n].builtin]
    return names

unregister

unregister(name: str) -> None

Remove a strategy from the registry (no error if absent).

Source code in src/jaxfolio/registry.py
def unregister(name: str) -> None:
    """Remove a strategy from the registry (no error if absent)."""
    _REGISTRY.pop(name, None)

registry

registry() -> dict[str, StrategyInfo]

Return a shallow copy of the registry mapping (for inspection).

Source code in src/jaxfolio/registry.py
def registry() -> dict[str, StrategyInfo]:
    """Return a shallow copy of the registry mapping (for inspection)."""
    return dict(_REGISTRY)